change(skills): move basic demo skills into main

This commit is contained in:
zhouli
2026-04-12 17:38:22 +08:00
parent d64eed5773
commit b68e8f99d1
19 changed files with 30 additions and 799 deletions
+1
View File
@@ -0,0 +1 @@
fatfs_image/skills
@@ -1,80 +0,0 @@
# Feishu Messaging
Use this skill when the user wants to interact through Feishu, especially to reply in the current Feishu conversation or send local files back to Feishu.
## When to use
- The user sends a message from Feishu and expects a reply in the same chat.
- The user asks to send a text, image, document, report, log, or other local file to a Feishu chat.
- The task is clearly Feishu-specific rather than QQ, Telegram, or WeChat.
## Available capabilities
- `feishu_send_message`: send plain text to a Feishu chat.
- `feishu_send_image`: send a local image file to a Feishu chat.
- `feishu_send_file`: send a local non-image file to a Feishu chat.
## Calling rules
- Call the direct Feishu capabilities. Do not route Feishu messaging through `cap_cli`.
- When replying to the current inbound Feishu conversation, you can omit `chat_id` if the runtime context already contains it.
- When starting a new outbound send or the target chat is ambiguous, pass an explicit `chat_id`.
- Use `feishu_send_message` for text only.
- Use `feishu_send_image` for image files such as `.jpg`, `.jpeg`, `.png`, `.gif`, or `.webp`.
- Use `feishu_send_file` for non-image files such as `.txt`, `.json`, `.log`, `.csv`, `.pdf`, or archives.
- `caption` is optional for image and file sends. In Feishu media send flow, caption is sent as a follow-up text message.
## Chat ID guidance
- Feishu text send accepts either a chat id or a user `open_id`.
- If the target begins with `ou_`, the runtime treats it as a user `open_id`.
- Otherwise it is treated as a Feishu `chat_id`.
- For replies triggered by an inbound Feishu message, prefer using the current context instead of inventing a `chat_id`.
## File path guidance
- `path` must be a real local filesystem path on the device.
- If the exact path is unknown, inspect storage first with file capabilities such as `list_dir`.
- Do not pass remote URLs directly to Feishu send capabilities.
- In this demo app, inbound Feishu attachments are typically saved under `/fatfs/data/inbox`.
## Recommended workflow
1. Determine whether the user wants text, an image, or a generic file.
2. Resolve the target Feishu chat or `open_id`.
3. Resolve the local file path if sending media.
4. Call `feishu_send_message`, `feishu_send_image`, or `feishu_send_file` directly.
5. After the capability returns success, tell the user the reply or file has already been sent.
## Examples
Reply with text to the current Feishu chat:
```json
{
"message": "The task has been completed."
}
```
Send text to an explicit Feishu user:
```json
{
"chat_id": "ou_xxx123456",
"message": "Latest status: device is online."
}
```
Send an image:
```json
{
"chat_id": "oc_xxx123456",
"path": "/fatfs/data/inbox/capture.jpg",
"caption": "Here is the image."
}
```
Send a file:
```json
{
"chat_id": "oc_xxx123456",
"path": "/fatfs/data/reports/status.json",
"caption": "Latest report."
}
```
## Notes
- This skill is for Feishu only. If the user is on another IM channel, use that channel's capability group instead.
- Feishu send capabilities return JSON such as `{\"ok\":true}` on success.
@@ -1,81 +0,0 @@
# QQ Messaging
Use this skill when the user wants to interact through QQ Bot channels, especially to reply in the current QQ conversation or send local files back to QQ.
## When to use
- The user sends a message from QQ and expects a reply in the same chat.
- The user asks to send a text, image, screenshot, report, log, or other local file to a QQ chat.
- The task is clearly QQ-specific rather than Telegram, Feishu, or WeChat.
## Available capabilities
- `qq_send_message`: send plain text to a QQ chat.
- `qq_send_image`: send a local image file to a QQ chat.
- `qq_send_file`: send a local non-image file to a QQ chat.
## Calling rules
- Call the direct QQ capabilities. Do not route QQ messaging through `cap_cli`.
- When replying to the current inbound QQ conversation, you can omit `chat_id` if the runtime context already contains it.
- When starting a new outbound send or the target chat is ambiguous, pass an explicit `chat_id`.
- Use `qq_send_message` for text only.
- Use `qq_send_image` for image files such as `.jpg`, `.jpeg`, `.png`, `.gif`, or `.webp`.
- Use `qq_send_file` for non-image files such as `.txt`, `.json`, `.log`, `.csv`, `.pdf`, or archives.
- `caption` is optional for image and file sends. Include it only when the user wants accompanying text.
## Chat ID guidance
- Private QQ chats are normalized as `c2c:<openid>`.
- Group QQ chats are normalized as `group:<group_openid>`.
- For replies triggered by an inbound QQ message, prefer using the current context instead of inventing a `chat_id`.
- If the user explicitly provides a QQ target, preserve it exactly when it already matches the expected `c2c:` or `group:` format.
## File path guidance
- `path` must be a real local filesystem path on the device.
- If the exact path is unknown, inspect storage first with file capabilities such as `list_dir`.
- Do not attempt to send remote URLs directly. Download or locate the file on local storage first.
- In this demo app, inbound QQ attachments are typically saved under `/fatfs/data/inbox`.
## Recommended workflow
1. Determine whether the user wants text, an image, or a generic file.
2. Resolve the target chat.
3. Resolve the local file path if sending media.
4. Call `qq_send_message`, `qq_send_image`, or `qq_send_file` directly.
5. After the capability returns success, tell the user the reply or file has already been sent.
## Examples
Reply with text to the current QQ chat:
```json
{
"message": "The task has been completed."
}
```
Send text to an explicit QQ group:
```json
{
"chat_id": "c2c:1234567890",
"message": "Latest status: device is online."
}
```
Send an image:
```json
{
"chat_id": "c2c:1234567890",
"path": "/fatfs/data/inbox/capture.jpg",
"caption": "Here is the image."
}
```
Send a file:
```json
{
"chat_id": "c2c:abcdefg123456",
"path": "/fatfs/data/reports/status.json",
"caption": "Latest report."
}
```
## Notes
- This skill is for QQ only. If the user is on another IM channel
- The capability returns success text like `reply already sent to user`; do not repeat the same content again as if it still needs to be delivered.
- Generic file delivery may still depend on QQ platform-side support. If `qq_send_file` fails, report the failure clearly and consider whether the file can be sent as an image instead.
@@ -1,79 +0,0 @@
# Telegram Messaging
Use this skill when the user wants to interact through Telegram, especially to reply in the current Telegram conversation or send local files back to Telegram.
## When to use
- The user sends a message from Telegram and expects a reply in the same chat.
- The user asks to send a text, image, screenshot, report, log, or other local file to a Telegram chat.
- The task is clearly Telegram-specific rather than QQ, Feishu, or WeChat.
## Available capabilities
- `tg_send_message`: send plain text to a Telegram chat.
- `tg_send_image`: send a local image file to a Telegram chat.
- `tg_send_file`: send a local non-image file to a Telegram chat.
## Calling rules
- Call the direct Telegram capabilities. Do not route Telegram messaging through `cap_cli`.
- When replying to the current inbound Telegram conversation, you can omit `chat_id` if the runtime context already contains it.
- When starting a new outbound send or the target chat is ambiguous, pass an explicit `chat_id`.
- Use `tg_send_message` for text only.
- Use `tg_send_image` for image files such as `.jpg`, `.jpeg`, `.png`, `.gif`, or `.webp`.
- Use `tg_send_file` for non-image files such as `.txt`, `.json`, `.log`, `.csv`, `.pdf`, or archives.
- `caption` is optional for image and file sends. Include it only when the user wants accompanying text.
## Chat ID guidance
- Telegram `chat_id` is usually a numeric string such as `"123456789"` or `"-1001234567890"`.
- For replies triggered by an inbound Telegram message, prefer using the current context instead of reconstructing the `chat_id`.
- If the user gives a Telegram target explicitly, preserve it exactly.
## File path guidance
- `path` must be a real local filesystem path on the device.
- If the exact path is unknown, inspect storage first with file capabilities such as `list_dir`.
- Do not pass remote URLs directly to Telegram send capabilities.
- In this demo app, inbound Telegram attachments are typically saved under `/fatfs/data/inbox`.
## Recommended workflow
1. Determine whether the user wants text, an image, or a generic file.
2. Resolve the target Telegram chat.
3. Resolve the local file path if sending media.
4. Call `tg_send_message`, `tg_send_image`, or `tg_send_file` directly.
5. After the capability returns success, tell the user the reply or file has already been sent.
## Examples
Reply with text to the current Telegram chat:
```json
{
"message": "Task completed."
}
```
Send text to an explicit Telegram chat:
```json
{
"chat_id": "-1001234567890",
"message": "Latest status: device is online."
}
```
Send an image:
```json
{
"chat_id": "123456789",
"path": "/fatfs/data/inbox/capture.jpg",
"caption": "Here is the image."
}
```
Send a file:
```json
{
"chat_id": "123456789",
"path": "/fatfs/data/reports/status.json",
"caption": "Latest report."
}
```
## Notes
- This skill is for Telegram only. If the user is on another IM channel, use that channel's capability group instead.
- The capability returns success text like `reply already sent to user`; do not phrase the result as a pending action.
@@ -1,63 +0,0 @@
# WeChat Messaging
Use this skill when the user wants to interact through WeChat, especially to send text or a local image to a WeChat contact or group.
## When to use
- The user asks to send a text reply or image through WeChat.
- The task is clearly WeChat-specific rather than QQ, Telegram, or Feishu.
- The target WeChat `chat_id` is known or can be taken from the current workflow context outside the capability call.
## Available capabilities
- `wechat_send_message`: send plain text to a WeChat chat.
- `wechat_send_image`: send a local image file to a WeChat chat.
- `wechat_gateway`: WeChat inbound event source. This is infrastructure, not a capability the model should call directly.
## Calling rules
- Call the direct WeChat capabilities. Do not route WeChat messaging through `cap_cli`.
- `wechat_send_message` requires explicit `chat_id` and `message`.
- `wechat_send_image` requires explicit `chat_id` and `path`, with optional `caption`.
- Unlike Telegram, QQ, and Feishu, the current WeChat callable implementation does not fall back to `ctx->chat_id`. Always pass `chat_id` explicitly.
- Use `wechat_send_message` for text only.
- Use `wechat_send_image` for image files such as `.jpg`, `.jpeg`, `.png`, `.gif`, or `.webp`.
## Chat ID guidance
- WeChat inbound routing uses the group id when present, otherwise the sender user id.
- In practice, `chat_id` is typically a room id or a user id string already known by the integration.
- If the user gives a concrete WeChat target such as a room id or contact id, preserve it exactly.
## File path guidance
- `path` must be a real local filesystem path on the device.
- If the exact image path is unknown, inspect storage first with file capabilities such as `list_dir`.
- Do not pass remote URLs directly to WeChat send capabilities.
- In this demo app, inbound WeChat media is typically saved under `/fatfs/data/inbox`.
## Recommended workflow
1. Confirm that the target channel is WeChat.
2. Resolve the explicit WeChat `chat_id`.
3. Resolve the local image path if sending media.
4. Call `wechat_send_message` or `wechat_send_image` directly.
5. After the capability returns success, tell the user the message or image has already been sent.
## Examples
Send text to a WeChat chat:
```json
{
"chat_id": "room123",
"message": "Latest status: device is online."
}
```
Send an image:
```json
{
"chat_id": "wxid_abc123",
"path": "/fatfs/data/inbox/capture.jpg",
"caption": "Here is the image."
}
```
## Notes
- This skill is for WeChat only. If the user is on another IM channel, use that channel's capability group instead.
- The current capability surface supports text and image send, but not generic non-image file send.
- WeChat send capabilities return `{\"ok\":true}` on success.
@@ -1,35 +0,0 @@
# Image Inspection
Use this skill when the user wants the device to inspect a local image and describe what is visible.
## When to use
- The user asks what is in an image, photo, screenshot, or camera frame.
- The image already exists on the device filesystem.
- The task needs visual analysis rather than plain file reading.
## Available capability
- `inspect_image`: analyze one local image from an absolute path using a prompt that says what to inspect.
## Calling rules
- Call `inspect_image` directly.
- Always pass an absolute local file path in `path`.
- Always pass a clear `prompt` that tells the model what to look for.
- Confirm or discover the image path first if it is not already known.
- Do not pass remote URLs or non-image files.
## Path guidance
- Prefer real local paths already stored on the device.
- If the exact path is unknown, inspect storage first with file capabilities such as `list_dir`.
- Common roots in this demo include `/fatfs/data/inbox`, `/fatfs/data`, or other application-managed storage paths.
## Example
```json
{
"path": "/fatfs/data/inbox/photo.jpg",
"prompt": "Describe the main objects in this image and mention any visible text."
}
```
## Notes
- Keep the prompt specific. For example: identify objects, read visible text, describe a scene, or check whether a target item appears.
- If the image is blurry or uncertain, report that uncertainty instead of over-claiming.
@@ -1,69 +0,0 @@
# Lua Script Execution
Use this skill when the user wants to see existing Lua scripts, run one, or inspect async execution jobs.
## Command Rule
- The LLM should call Lua through the direct capability execute entrypoints, not through `cap_cli`.
- Use `lua_list_scripts` to inspect scripts.
- Use `lua_run_script` for synchronous execution.
- Use `lua_run_script_async` for long-running or continuous scripts.
- Use `lua_list_async_jobs` and `lua_get_async_job` to inspect async jobs.
## Running a Script Synchronously
Use `lua_run_script` when the user wants immediate output.
- Required: `path`
- Optional: `args`, `timeout_ms`
- Prefer relative paths such as `hello.lua`
Examples:
```json
{
"path": "hello.lua"
}
```
```json
{
"path": "blink.lua",
"args": {
"pin": 2
},
"timeout_ms": 3000
}
```
If the script expects structured inputs, pass them through `args`. The runtime exposes them to Lua as the global `args`.
## Running a Script Asynchronously
Use `lua_run_script_async` for long-running or continuous scripts.
- Required: `path`
- Optional: `args`, `timeout_ms`
Examples:
```json
{
"path": "blink.lua"
}
```
```json
{
"path": "blink.lua",
"args": {
"pin": 2
},
"timeout_ms": 3000
}
```
After starting an async script:
- Use `lua_list_async_jobs`
- Use `lua_list_async_jobs` with `{"status":"running"}`
- Use `lua_get_async_job` with `{"job_id":"<job_id>"}`
## Execution Notes
- Paths must resolve under `/spiffs/lua` and end with `.lua`.
- `--timeout-ms` must be a positive integer when provided.
- Prefer synchronous run for short scripts that should finish and return text.
- Prefer async run for loops, animations, watchers, or long-running device behaviors.
- If the user asks to run a script that does not exist yet, switch to the Lua authoring flow first.
@@ -1,14 +0,0 @@
# Lua Delay
This skill describes how to correctly use delay when writing Lua scripts.
## How to call
- Import it with `local delay = require("delay")`
- Call `delay.delay_ms(ms)` to sleep for a number of milliseconds
- `ms` should be an integer greater than or equal to `0`
## Example
```lua
local delay = require("delay")
delay.delay_ms(500)
```
@@ -1,21 +0,0 @@
# Lua Event Publisher
This skill describes how to correctly use event_publisher when writing Lua scripts.
## How to call
- Import it with `local event_publisher = require("event_publisher")`
- Call `event_publisher.publish_message({...})` to publish a message event
- Call `event_publisher.publish_trigger({...})` to publish a trigger event
- Call `event_publisher.publish({...})` to publish a full custom event
## Example
```lua
local event_publisher = require("event_publisher")
event_publisher.publish_message({
source_cap = "lua_script",
channel = "custom",
chat_id = "demo",
text = "hello"
})
```
@@ -1,16 +0,0 @@
# Lua GPIO
This skill describes how to correctly use gpio when writing Lua scripts.
## How to call
- Import it with `local gpio = require("gpio")`
- Call `gpio.set_direction(pin, mode)` to set pin mode
- Call `gpio.set_level(pin, level)` to set output level
- Call `gpio.get_level(pin)` to read pin level
## Example
```lua
local gpio = require("gpio")
gpio.set_direction(2, "output")
gpio.set_level(2, 1)
```
@@ -1,19 +0,0 @@
# Lua LED Strip
This skill describes how to correctly use led_strip when writing Lua scripts.
## How to call
- Import it with `local led_strip = require("led_strip")`
- Call `local strip = led_strip.new(gpio, max_leds)` to create a strip handle
- Call `strip:set_pixel(index, r, g, b)` to set one pixel
- Call `strip:refresh()` to apply changes
- Call `strip:clear()` or `strip:close()` when needed
## Example
```lua
local led_strip = require("led_strip")
local strip = led_strip.new(8, 1)
strip:set_pixel(0, 255, 0, 0)
strip:refresh()
```
@@ -1,18 +0,0 @@
# Lua Storage
This skill describes how to correctly use storage when writing Lua scripts.
## How to call
- Import it with `local storage = require("storage")`
- Call `storage.mkdir(path)` to create a directory
- Call `storage.write_file(path, content)` to write a file
- Call `storage.read_file(path)` to read a file
## Example
```lua
local storage = require("storage")
storage.mkdir("/fatfs/data/demo")
storage.write_file("/fatfs/data/demo/test.txt", "hello")
local text = storage.read_file("/fatfs/data/demo/test.txt")
```
@@ -1,69 +0,0 @@
# Lua Script Execution
Use this skill when the user wants to see existing Lua scripts, run one, or inspect async execution jobs.
## Command Rule
- The LLM should call Lua through the direct capability execute entrypoints.
- Use `lua_list_scripts` to inspect scripts.
- Use `lua_run_script` for synchronous execution.
- Use `lua_run_script_async` for long-running or continuous scripts.
- Use `lua_list_async_jobs` and `lua_get_async_job` to inspect async jobs.
## Running a Script Synchronously
Use `lua_run_script` when the user wants immediate output.
- Required: `path`
- Optional: `args`, `timeout_ms`
- Prefer relative paths such as `hello.lua`
Examples:
```json
{
"path": "hello.lua"
}
```
```json
{
"path": "blink.lua",
"args": {
"pin": 2
},
"timeout_ms": 3000
}
```
If the script expects structured inputs, pass them through `args`. The runtime exposes them to Lua as the global `args`.
## Running a Script Asynchronously
Use `lua_run_script_async` for long-running or continuous scripts.
- Required: `path`
- Optional: `args`, `timeout_ms`
Examples:
```json
{
"path": "blink.lua"
}
```
```json
{
"path": "blink.lua",
"args": {
"pin": 2
},
"timeout_ms": 3000
}
```
After starting an async script:
- Use `lua_list_async_jobs`
- Use `lua_list_async_jobs` with `{"status":"running"}`
- Use `lua_get_async_job` with `{"job_id":"<job_id>"}`
## Execution Notes
- Paths must resolve under `/spiffs/lua` and end with `.lua`.
- `--timeout-ms` must be a positive integer when provided.
- Prefer synchronous run for short scripts that should finish and return text.
- Prefer async run for loops, animations, watchers, or long-running device behaviors.
- If the user asks to run a script that does not exist yet, switch to the Lua authoring flow first.
@@ -1,64 +0,0 @@
# Lua Script Authoring
Use this skill when the user wants to write, generate, or modify a managed Lua script for this device.
## Runtime Constraints
- Managed Lua scripts must stay under `/spiffs/lua`.
- The script path must end with `.lua`.
- Prefer relative paths such as `blink.lua` or `rainbow.lua`; the runtime resolves them under `/spiffs/lua`.
- Use `lua_write_script` to save or overwrite script content.
## Available Lua Modules
The runtime includes these built-in and application-registered modules:
### `delay`
- `delay.delay_ms(ms)`
- Use for short blocking delays inside a script.
### `storage`
- `storage.mkdir(path)`
- `storage.write_file(path, content)`
- `storage.read_file(path)`
- Use only for files the script needs to manage.
### `gpio`
- `gpio.set_direction(pin, mode)`
- `gpio.set_level(pin, level)`
- `gpio.get_level(pin)`
- Supported modes: `input`, `output`, `input_output`, `output_od`, `input_output_od`, `disable`
### `led_strip`
- `local strip = led_strip.new(gpio_pin, max_leds)`
- `strip:set_pixel(index, r, g, b)`
- `strip:refresh()`
- `strip:clear()`
- `strip:close()`
- This is for WS2812-style LED strips on a GPIO pin.
## Writing Guidance
- Write plain Lua script files, not markdown or pseudocode.
- Keep dependencies limited to standard Lua plus the modules listed above.
- Prefer small scripts with a clear entry flow and explicit comments for pin usage.
- If the script touches GPIO or LED hardware, state the pin numbers and expected electrical behavior in comments.
- If a requested peripheral is not covered by `gpio` or `led_strip`, say that the current runtime does not expose that peripheral module.
## Example Shape
```lua
local gpio = require("gpio")
local delay = require("delay")
gpio.set_direction(2, "output")
while true do
gpio.set_level(2, 1)
delay.delay_ms(500)
gpio.set_level(2, 0)
delay.delay_ms(500)
end
```
## Save Rule
When the script is ready, call `lua_write_script` with:
- `path`: relative `.lua` path under `/spiffs/lua`
- `content`: full Lua source
- `overwrite`: `true` only when replacing an existing script intentionally
@@ -1,55 +0,0 @@
# QQ File Return
Use this skill when the user wants the device to send a local file or image back to a QQ chat.
## When to use
- The user asks to send back a file, attachment, image, photo, log, or generated output through QQ.
- The target conversation is already the active QQ chat, or the user provides an explicit QQ `chat_id`.
## Available tools
- `list_dir`: inspect device storage and confirm the file path
- `read_file`: inspect small text files before sending when needed
- Direct QQ capabilities: `qq_send_image` and `qq_send_file`
## Path guidance
- Prefer real local paths already stored on the device.
- Common roots in this demo are `/spiffs`, `/spiffs/qq`, `/spiffs/lua`, or other application-managed storage paths.
- Use `list_dir` first if the exact file path is unknown.
- Use `read_file` only for small text inspection, not for binary payloads.
## Sending rules
- Use `qq_send_image` for image files such as `.jpg`, `.jpeg`, `.png`, `.gif`, or `.webp`.
- Use `qq_send_file` for non-image files such as `.txt`, `.json`, `.log`, `.csv`, or archives.
- Execute the chosen QQ capability directly.
- Pass `caption` only when the user wants an accompanying message.
- The JSON payload should include an explicit QQ `chat_id`, `path`, and optional `caption`.
## Examples
Send an image to a QQ chat:
```json
{
"chat_id": "group123",
"path": "/spiffs/qq/capture.jpg",
"caption": "Here is the image."
}
```
Send a file to a QQ group:
```json
{
"chat_id": "group1234567890",
"path": "/spiffs/reports/status.json",
"caption": "Latest status report."
}
```
## Workflow
1. Confirm the target file exists with `list_dir` if needed.
2. Choose `qq_send_image` or `qq_send_file` based on file type.
3. Execute the QQ capability directly with `chat_id`, `path`, and optional `caption`.
4. Tell the user whether the send succeeded.
## Notes
- This skill only sends files that already exist on the device filesystem.
- QQ generic file delivery may depend on platform-side enablement. If `qq_send_file` fails, prefer falling back to `qq_send_image` for images or explain that QQ rejected generic file upload.
@@ -1,103 +0,0 @@
{
"skills": [
{
"id": "cap_im_feishu",
"file": "cap_im_feishu.md",
"title": "cap_im_feishu",
"summary": "How to reply and send text, images, and files through Feishu capabilities.",
"cap_groups": [
"cap_im_feishu"
]
},
{
"id": "cap_im_qq",
"file": "cap_im_qq.md",
"title": "cap_im_qq",
"summary": "Reply in the QQ conversation or send local files back to QQ",
"cap_groups": [
"cap_im_qq"
]
},
{
"id": "cap_im_tg",
"file": "cap_im_tg.md",
"title": "cap_im_tg",
"summary": "How to reply and send text, images, and files through Telegram capabilities.",
"cap_groups": [
"cap_im_tg"
]
},
{
"id": "cap_im_wechat",
"file": "cap_im_wechat.md",
"title": "cap_im_wechat",
"summary": "How to send text and images through WeChat capabilities.",
"cap_groups": [
"cap_im_wechat"
]
},
{
"id": "cap_llm_inspect_image",
"file": "cap_llm_inspect.md",
"title": "cap_llm_inspect_image",
"summary": "How to inspect a local image with inspect_image.",
"cap_groups": [
"cap_llm_inspect"
]
},
{
"id": "cap_lua_run",
"file": "cap_lua_run.md",
"title": "cap_lua_run",
"summary": "This Files describes how to correctly use Lua scripts.",
"cap_groups": [
"cap_lua"
]
},
{
"id": "lua_module_delay",
"file": "lua_module_delay.md",
"title": "lua_module_delay",
"summary": "How to call delay.delay_ms(ms) from Lua after loading the delay module.",
"cap_groups": [
"cap_lua"
]
},
{
"id": "lua_module_event_publisher",
"file": "lua_module_event_publisher.md",
"title": "lua_module_event_publisher",
"summary": "How to publish message, trigger, and custom events from Lua.",
"cap_groups": [
"cap_lua"
]
},
{
"id": "lua_module_gpio",
"file": "lua_module_gpio.md",
"title": "lua_module_gpio",
"summary": "How to set GPIO direction, write level, and read level from Lua.",
"cap_groups": [
"cap_lua"
]
},
{
"id": "lua_module_led_strip",
"file": "lua_module_led_strip.md",
"title": "lua_module_led_strip",
"summary": "How to create and control an LED strip from Lua.",
"cap_groups": [
"cap_lua"
]
},
{
"id": "lua_module_storage",
"file": "lua_module_storage.md",
"title": "lua_module_storage",
"summary": "How to create directories and read or write files from Lua.",
"cap_groups": [
"cap_lua"
]
}
]
}
@@ -0,0 +1,13 @@
{
"skills": [
{
"id": "weather_search",
"file": "weather.md",
"title": "weather",
"summary": "How to answer current weather, temperature, and forecast queries through direct web search capabilities.",
"cap_groups": [
"web_search"
]
}
]
}
@@ -43,9 +43,9 @@ def load_json_file(path: Path) -> object:
try:
return json.loads(path.read_text(encoding='utf-8'))
except FileNotFoundError:
fail(f"Missing JSON file: {path}")
fail(f'Missing JSON file: {path}')
except json.JSONDecodeError as exc:
fail(f"Invalid JSON in {path}: {exc}")
fail(f'Invalid JSON in {path}: {exc}')
def load_manifest(path: Path) -> dict:
@@ -54,7 +54,7 @@ def load_manifest(path: Path) -> dict:
data = load_json_file(path)
if not isinstance(data, dict):
fail(f"Manifest must be a JSON object: {path}")
fail(f'Manifest must be a JSON object: {path}')
return {
'component_entries': list(data.get('component_entries', [])),
'component_files': list(data.get('component_files', [])),
@@ -109,9 +109,12 @@ def recover_demo_entries(
def load_demo_entries(skills_list_path: Path, manifest: dict, component_entries: list[dict]) -> list[dict]:
if not skills_list_path.exists():
return []
data = load_json_file(skills_list_path)
if not isinstance(data, dict):
fail(f"{skills_list_path} must be a JSON object.")
fail(f'{skills_list_path} must be a JSON object.')
skills = data.get('skills')
if not isinstance(skills, list):
@@ -140,7 +143,7 @@ def collect_component_skills(
data = load_json_file(json_path)
if not isinstance(data, dict):
fail(f"{json_path} must be a JSON object.")
fail(f'{json_path} must be a JSON object.')
skills = data.get('skills')
if not isinstance(skills, list):
@@ -161,17 +164,17 @@ def collect_component_skills(
if skill_path.parent != skills_dir.resolve():
fail(
f"Component '{component_name}' declares skill file outside its skills directory: "
f"{skill_file} ({json_path})"
f'{skill_file} ({json_path})'
)
if not skill_path.is_file():
fail(
f"Component '{component_name}' references missing skill file '{skill_file}' "
f"in {json_path}"
f'in {json_path}'
)
if skill_path.suffix.lower() != '.md':
fail(
f"Component '{component_name}' declares non-markdown skill file '{skill_file}' "
f"in {json_path}"
f'in {json_path}'
)
previous_id = skill_id_sources.get(skill_id)
@@ -233,7 +236,7 @@ def merge_entries(
for entry in demo_entries:
skill_file = entry.get('file')
register(entry, f"demo skills list ({demo_skills_dir / skill_file})")
register(entry, f'demo skills list ({demo_skills_dir / skill_file})')
for entry in component_entries:
entry_key = (str(entry.get('id')), str(entry.get('file')))
@@ -249,7 +252,7 @@ def validate_demo_markdown_files(demo_entries: list[dict], demo_skills_dir: Path
if not skill_path.is_file():
fail(
f"Demo skills_list.json references missing skill file '{skill_file}' "
f"at {skill_path}"
f'at {skill_path}'
)
@@ -261,6 +264,8 @@ def sync_markdown_files(
merged_entries: list[dict],
component_entries: list[dict],
) -> None:
demo_skills_dir.mkdir(parents=True, exist_ok=True)
for old_file in manifest.get('component_files', []):
if old_file not in copy_map:
stale_path = demo_skills_dir / old_file
@@ -290,8 +295,6 @@ def sync_markdown_files(
def main() -> int:
args = parse_args()
demo_skills_dir = Path(args.demo_skills_dir).resolve()
if not demo_skills_dir.is_dir():
fail(f"Demo skills directory does not exist: {demo_skills_dir}")
skills_list_path = demo_skills_dir / SKILLS_LIST_FILE
manifest_path = Path(args.manifest_path).resolve()
@@ -324,5 +327,5 @@ if __name__ == '__main__':
try:
sys.exit(main())
except SkillSyncError as exc:
print(f"sync_component_skills.py: error: {exc}", file=sys.stderr)
print(f'sync_component_skills.py: error: {exc}', file=sys.stderr)
sys.exit(1)