mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-26 01:01:40 +00:00
docs(website): dedicated page per bundled + optional skill (#14929)
Generates a full dedicated Docusaurus page for every one of the 132 skills
(73 bundled + 59 optional) under website/docs/user-guide/skills/{bundled,optional}/<category>/.
Each page carries the skill's description, metadata (version, author, license,
dependencies, platform gating, tags, related skills cross-linked to their own
pages), and the complete SKILL.md body that Hermes loads at runtime.
Previously the two catalog pages just listed skills with a one-line blurb and
no way to see what the skill actually did — users had to go read the source
repo. Now every skill has a browsable, searchable, cross-linked reference in
the docs.
- website/scripts/generate-skill-docs.py — generator that reads skills/ and
optional-skills/, writes per-skill pages, regenerates both catalog indexes,
and rewrites the Skills section of sidebars.ts. Handles MDX escaping
(outside fenced code blocks: curly braces, unsafe HTML-ish tags) and
rewrites relative references/*.md links to point at the GitHub source.
- website/docs/reference/skills-catalog.md — regenerated; each row links to
the new dedicated page.
- website/docs/reference/optional-skills-catalog.md — same.
- website/sidebars.ts — Skills section now has Bundled / Optional subtrees
with one nested category per skill folder.
- .github/workflows/{docs-site-checks,deploy-site}.yml — run the generator
before docusaurus build so CI stays in sync with the source SKILL.md files.
Build verified locally with `npx docusaurus build`. Only remaining warnings
are pre-existing broken link/anchor issues in unrelated pages.
This commit is contained in:
parent
eb93f88e1d
commit
0f6eabb890
139 changed files with 43523 additions and 306 deletions
|
|
@ -0,0 +1,113 @@
|
|||
---
|
||||
title: "Canvas — Canvas LMS integration — fetch enrolled courses and assignments using API token authentication"
|
||||
sidebar_label: "Canvas"
|
||||
description: "Canvas LMS integration — fetch enrolled courses and assignments using API token authentication"
|
||||
---
|
||||
|
||||
{/* This page is auto-generated from the skill's SKILL.md by website/scripts/generate-skill-docs.py. Edit the source SKILL.md, not this page. */}
|
||||
|
||||
# Canvas
|
||||
|
||||
Canvas LMS integration — fetch enrolled courses and assignments using API token authentication.
|
||||
|
||||
## Skill metadata
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| Source | Optional — install with `hermes skills install official/productivity/canvas` |
|
||||
| Path | `optional-skills/productivity/canvas` |
|
||||
| Version | `1.0.0` |
|
||||
| Author | community |
|
||||
| License | MIT |
|
||||
| Tags | `Canvas`, `LMS`, `Education`, `Courses`, `Assignments` |
|
||||
|
||||
## Reference: full SKILL.md
|
||||
|
||||
:::info
|
||||
The following is the complete skill definition that Hermes loads when this skill is triggered. This is what the agent sees as instructions when the skill is active.
|
||||
:::
|
||||
|
||||
# Canvas LMS — Course & Assignment Access
|
||||
|
||||
Read-only access to Canvas LMS for listing courses and assignments.
|
||||
|
||||
## Scripts
|
||||
|
||||
- `scripts/canvas_api.py` — Python CLI for Canvas API calls
|
||||
|
||||
## Setup
|
||||
|
||||
1. Log in to your Canvas instance in a browser
|
||||
2. Go to **Account → Settings** (click your profile icon, then Settings)
|
||||
3. Scroll to **Approved Integrations** and click **+ New Access Token**
|
||||
4. Name the token (e.g., "Hermes Agent"), set an optional expiry, and click **Generate Token**
|
||||
5. Copy the token and add to `~/.hermes/.env`:
|
||||
|
||||
```
|
||||
CANVAS_API_TOKEN=your_token_here
|
||||
CANVAS_BASE_URL=https://yourschool.instructure.com
|
||||
```
|
||||
|
||||
The base URL is whatever appears in your browser when you're logged into Canvas (no trailing slash).
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
CANVAS="python $HERMES_HOME/skills/productivity/canvas/scripts/canvas_api.py"
|
||||
|
||||
# List all active courses
|
||||
$CANVAS list_courses --enrollment-state active
|
||||
|
||||
# List all courses (any state)
|
||||
$CANVAS list_courses
|
||||
|
||||
# List assignments for a specific course
|
||||
$CANVAS list_assignments 12345
|
||||
|
||||
# List assignments ordered by due date
|
||||
$CANVAS list_assignments 12345 --order-by due_at
|
||||
```
|
||||
|
||||
## Output Format
|
||||
|
||||
**list_courses** returns:
|
||||
```json
|
||||
[{"id": 12345, "name": "Intro to CS", "course_code": "CS101", "workflow_state": "available", "start_at": "...", "end_at": "..."}]
|
||||
```
|
||||
|
||||
**list_assignments** returns:
|
||||
```json
|
||||
[{"id": 67890, "name": "Homework 1", "due_at": "2025-02-15T23:59:00Z", "points_possible": 100, "submission_types": ["online_upload"], "html_url": "...", "description": "...", "course_id": 12345}]
|
||||
```
|
||||
|
||||
Note: Assignment descriptions are truncated to 500 characters. The `html_url` field links to the full assignment page in Canvas.
|
||||
|
||||
## API Reference (curl)
|
||||
|
||||
```bash
|
||||
# List courses
|
||||
curl -s -H "Authorization: Bearer $CANVAS_API_TOKEN" \
|
||||
"$CANVAS_BASE_URL/api/v1/courses?enrollment_state=active&per_page=10"
|
||||
|
||||
# List assignments for a course
|
||||
curl -s -H "Authorization: Bearer $CANVAS_API_TOKEN" \
|
||||
"$CANVAS_BASE_URL/api/v1/courses/COURSE_ID/assignments?per_page=10&order_by=due_at"
|
||||
```
|
||||
|
||||
Canvas uses `Link` headers for pagination. The Python script handles pagination automatically.
|
||||
|
||||
## Rules
|
||||
|
||||
- This skill is **read-only** — it only fetches data, never modifies courses or assignments
|
||||
- On first use, verify auth by running `$CANVAS list_courses` — if it fails with 401, guide the user through setup
|
||||
- Canvas rate-limits to ~700 requests per 10 minutes; check `X-Rate-Limit-Remaining` header if hitting limits
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Problem | Fix |
|
||||
|---------|-----|
|
||||
| 401 Unauthorized | Token invalid or expired — regenerate in Canvas Settings |
|
||||
| 403 Forbidden | Token lacks permission for this course |
|
||||
| Empty course list | Try `--enrollment-state active` or omit the flag to see all states |
|
||||
| Wrong institution | Verify `CANVAS_BASE_URL` matches the URL in your browser |
|
||||
| Timeout errors | Check network connectivity to your Canvas instance |
|
||||
|
|
@ -0,0 +1,336 @@
|
|||
---
|
||||
title: "Memento Flashcards — Spaced-repetition flashcard system"
|
||||
sidebar_label: "Memento Flashcards"
|
||||
description: "Spaced-repetition flashcard system"
|
||||
---
|
||||
|
||||
{/* This page is auto-generated from the skill's SKILL.md by website/scripts/generate-skill-docs.py. Edit the source SKILL.md, not this page. */}
|
||||
|
||||
# Memento Flashcards
|
||||
|
||||
Spaced-repetition flashcard system. Create cards from facts or text, chat with flashcards using free-text answers graded by the agent, generate quizzes from YouTube transcripts, review due cards with adaptive scheduling, and export/import decks as CSV.
|
||||
|
||||
## Skill metadata
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| Source | Optional — install with `hermes skills install official/productivity/memento-flashcards` |
|
||||
| Path | `optional-skills/productivity/memento-flashcards` |
|
||||
| Version | `1.0.0` |
|
||||
| Author | Memento AI |
|
||||
| License | MIT |
|
||||
| Platforms | macos, linux |
|
||||
| Tags | `Education`, `Flashcards`, `Spaced Repetition`, `Learning`, `Quiz`, `YouTube` |
|
||||
|
||||
## Reference: full SKILL.md
|
||||
|
||||
:::info
|
||||
The following is the complete skill definition that Hermes loads when this skill is triggered. This is what the agent sees as instructions when the skill is active.
|
||||
:::
|
||||
|
||||
# Memento Flashcards — Spaced-Repetition Flashcard Skill
|
||||
|
||||
## Overview
|
||||
|
||||
Memento gives you a local, file-based flashcard system with spaced-repetition scheduling.
|
||||
Users can chat with their flashcards by answering in free text and having the agent grade the response before scheduling the next review.
|
||||
Use it whenever the user wants to:
|
||||
|
||||
- **Remember a fact** — turn any statement into a Q/A flashcard
|
||||
- **Study with spaced repetition** — review due cards with adaptive intervals and agent-graded free-text answers
|
||||
- **Quiz from a YouTube video** — fetch a transcript and generate a 5-question quiz
|
||||
- **Manage decks** — organise cards into collections, export/import CSV
|
||||
|
||||
All card data lives in a single JSON file. No external API keys are required — you (the agent) generate flashcard content and quiz questions directly.
|
||||
|
||||
User-facing response style for Memento Flashcards:
|
||||
- Use plain text only. Do not use Markdown formatting in replies to the user.
|
||||
- Keep review and quiz feedback brief and neutral. Avoid extra praise, pep, or long explanations.
|
||||
|
||||
## When to Use
|
||||
|
||||
Use this skill when the user wants to:
|
||||
- Save facts as flashcards for later review
|
||||
- Review due cards with spaced repetition
|
||||
- Generate a quiz from a YouTube video transcript
|
||||
- Import, export, inspect, or delete flashcard data
|
||||
|
||||
Do not use this skill for general Q&A, coding help, or non-memory tasks.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| User intent | Action |
|
||||
|---|---|
|
||||
| "Remember that X" / "save this as a flashcard" | Generate a Q/A card, call `memento_cards.py add` |
|
||||
| Sends a fact without mentioning flashcards | Ask "Want me to save this as a Memento flashcard?" — only create if confirmed |
|
||||
| "Create a flashcard" | Ask for Q, A, collection; call `memento_cards.py add` |
|
||||
| "Review my cards" | Call `memento_cards.py due`, present cards one-by-one |
|
||||
| "Quiz me on [YouTube URL]" | Call `youtube_quiz.py fetch VIDEO_ID`, generate 5 questions, call `memento_cards.py add-quiz` |
|
||||
| "Export my cards" | Call `memento_cards.py export --output PATH` |
|
||||
| "Import cards from CSV" | Call `memento_cards.py import --file PATH --collection NAME` |
|
||||
| "Show my stats" | Call `memento_cards.py stats` |
|
||||
| "Delete a card" | Call `memento_cards.py delete --id ID` |
|
||||
| "Delete a collection" | Call `memento_cards.py delete-collection --collection NAME` |
|
||||
|
||||
## Card Storage
|
||||
|
||||
Cards are stored in a JSON file at:
|
||||
|
||||
```
|
||||
~/.hermes/skills/productivity/memento-flashcards/data/cards.json
|
||||
```
|
||||
|
||||
**Never edit this file directly.** Always use `memento_cards.py` subcommands. The script handles atomic writes (write to temp file, then rename) to prevent corruption.
|
||||
|
||||
The file is created automatically on first use.
|
||||
|
||||
## Procedure
|
||||
|
||||
### Creating Cards from Facts
|
||||
|
||||
### Activation Rules
|
||||
|
||||
Not every factual statement should become a flashcard. Use this three-tier check:
|
||||
|
||||
1. **Explicit intent** — the user mentions "memento", "flashcard", "remember this", "save this card", "add a card", or similar phrasing that clearly requests a flashcard → **create the card directly**, no confirmation needed.
|
||||
2. **Implicit intent** — the user sends a factual statement without mentioning flashcards (e.g. "The speed of light is 299,792 km/s") → **ask first**: "Want me to save this as a Memento flashcard?" Only create the card if the user confirms.
|
||||
3. **No intent** — the message is a coding task, a question, instructions, normal conversation, or anything that is clearly not a fact to memorize → **do NOT activate this skill at all**. Let other skills or default behavior handle it.
|
||||
|
||||
When activation is confirmed (tier 1 directly, tier 2 after confirmation), generate a flashcard:
|
||||
|
||||
**Step 1:** Turn the statement into a Q/A pair. Use this format internally:
|
||||
|
||||
```
|
||||
Turn the factual statement into a front-back pair.
|
||||
Return exactly two lines:
|
||||
Q: <question text>
|
||||
A: <answer text>
|
||||
|
||||
Statement: "{statement}"
|
||||
```
|
||||
|
||||
Rules:
|
||||
- The question should test recall of the key fact
|
||||
- The answer should be concise and direct
|
||||
|
||||
**Step 2:** Call the script to store the card:
|
||||
|
||||
```bash
|
||||
python3 ~/.hermes/skills/productivity/memento-flashcards/scripts/memento_cards.py add \
|
||||
--question "What year did World War 2 end?" \
|
||||
--answer "1945" \
|
||||
--collection "History"
|
||||
```
|
||||
|
||||
If the user doesn't specify a collection, use `"General"` as the default.
|
||||
|
||||
The script outputs JSON confirming the created card.
|
||||
|
||||
### Manual Card Creation
|
||||
|
||||
When the user explicitly asks to create a flashcard, ask them for:
|
||||
1. The question (front of card)
|
||||
2. The answer (back of card)
|
||||
3. The collection name (optional — default to `"General"`)
|
||||
|
||||
Then call `memento_cards.py add` as above.
|
||||
|
||||
### Reviewing Due Cards
|
||||
|
||||
When the user wants to review, fetch all due cards:
|
||||
|
||||
```bash
|
||||
python3 ~/.hermes/skills/productivity/memento-flashcards/scripts/memento_cards.py due
|
||||
```
|
||||
|
||||
This returns a JSON array of cards where `next_review_at <= now`. If a collection filter is needed:
|
||||
|
||||
```bash
|
||||
python3 ~/.hermes/skills/productivity/memento-flashcards/scripts/memento_cards.py due --collection "History"
|
||||
```
|
||||
|
||||
**Review flow (free-text grading):**
|
||||
|
||||
Here is an example of the EXACT interaction pattern you must follow. The user answers, you grade them, tell them the correct answer, then rate the card.
|
||||
|
||||
**Example interaction:**
|
||||
|
||||
> **Agent:** What year did the Berlin Wall fall?
|
||||
>
|
||||
> **User:** 1991
|
||||
>
|
||||
> **Agent:** Not quite. The Berlin Wall fell in 1989. Next review is tomorrow.
|
||||
> *(agent calls: memento_cards.py rate --id ABC --rating hard --user-answer "1991")*
|
||||
>
|
||||
> Next question: Who was the first person to walk on the moon?
|
||||
|
||||
**The rules:**
|
||||
|
||||
1. Show only the question. Wait for the user to answer.
|
||||
2. After receiving their answer, compare it to the expected answer and grade it:
|
||||
- **correct** → user got the key fact right (even if worded differently)
|
||||
- **partial** → right track but missing the core detail
|
||||
- **incorrect** → wrong or off-topic
|
||||
3. **You MUST tell the user the correct answer and how they did.** Keep it short and plain-text. Use this format:
|
||||
- correct: "Correct. Answer: {answer}. Next review in 7 days."
|
||||
- partial: "Close. Answer: {answer}. {what they missed}. Next review in 3 days."
|
||||
- incorrect: "Not quite. Answer: {answer}. Next review tomorrow."
|
||||
4. Then call the rate command: correct→easy, partial→good, incorrect→hard.
|
||||
5. Then show the next question.
|
||||
|
||||
```bash
|
||||
python3 ~/.hermes/skills/productivity/memento-flashcards/scripts/memento_cards.py rate \
|
||||
--id CARD_ID --rating easy --user-answer "what the user said"
|
||||
```
|
||||
|
||||
**Never skip step 3.** The user must always see the correct answer and feedback before you move on.
|
||||
|
||||
If no cards are due, tell the user: "No cards due for review right now. Check back later!"
|
||||
|
||||
**Retire override:** At any point the user can say "retire this card" to permanently remove it from reviews. Use `--rating retire` for this.
|
||||
|
||||
### Spaced Repetition Algorithm
|
||||
|
||||
The rating determines the next review interval:
|
||||
|
||||
| Rating | Interval | ease_streak | Status change |
|
||||
|---|---|---|---|
|
||||
| **hard** | +1 day | reset to 0 | stays learning |
|
||||
| **good** | +3 days | reset to 0 | stays learning |
|
||||
| **easy** | +7 days | +1 | if ease_streak >= 3 → retired |
|
||||
| **retire** | permanent | reset to 0 | → retired |
|
||||
|
||||
- **learning**: card is actively in rotation
|
||||
- **retired**: card won't appear in reviews (user has mastered it or manually retired it)
|
||||
- Three consecutive "easy" ratings automatically retire a card
|
||||
|
||||
### YouTube Quiz Generation
|
||||
|
||||
When the user sends a YouTube URL and wants a quiz:
|
||||
|
||||
**Step 1:** Extract the video ID from the URL (e.g. `dQw4w9WgXcQ` from `https://www.youtube.com/watch?v=dQw4w9WgXcQ`).
|
||||
|
||||
**Step 2:** Fetch the transcript:
|
||||
|
||||
```bash
|
||||
python3 ~/.hermes/skills/productivity/memento-flashcards/scripts/youtube_quiz.py fetch VIDEO_ID
|
||||
```
|
||||
|
||||
This returns `{"title": "...", "transcript": "..."}` or an error.
|
||||
|
||||
If the script reports `missing_dependency`, tell the user to install it:
|
||||
```bash
|
||||
pip install youtube-transcript-api
|
||||
```
|
||||
|
||||
**Step 3:** Generate 5 quiz questions from the transcript. Use these rules:
|
||||
|
||||
```
|
||||
You are creating a 5-question quiz for a podcast episode.
|
||||
Return ONLY a JSON array with exactly 5 objects.
|
||||
Each object must contain keys 'question' and 'answer'.
|
||||
|
||||
Selection criteria:
|
||||
- Prioritize important, surprising, or foundational facts.
|
||||
- Skip filler, obvious details, and facts that require heavy context.
|
||||
- Never return true/false questions.
|
||||
- Never ask only for a date.
|
||||
|
||||
Question rules:
|
||||
- Each question must test exactly one discrete fact.
|
||||
- Use clear, unambiguous wording.
|
||||
- Prefer What, Who, How many, Which.
|
||||
- Avoid open-ended Describe or Explain prompts.
|
||||
|
||||
Answer rules:
|
||||
- Each answer must be under 240 characters.
|
||||
- Lead with the answer itself, not preamble.
|
||||
- Add only minimal clarifying detail if needed.
|
||||
```
|
||||
|
||||
Use the first 15,000 characters of the transcript as context. Generate the questions yourself (you are the LLM).
|
||||
|
||||
**Step 4:** Validate the output is valid JSON with exactly 5 items, each having non-empty `question` and `answer` strings. If validation fails, retry once.
|
||||
|
||||
**Step 5:** Store quiz cards:
|
||||
|
||||
```bash
|
||||
python3 ~/.hermes/skills/productivity/memento-flashcards/scripts/memento_cards.py add-quiz \
|
||||
--video-id "VIDEO_ID" \
|
||||
--questions '[{"question":"...","answer":"..."},...]' \
|
||||
--collection "Quiz - Episode Title"
|
||||
```
|
||||
|
||||
The script deduplicates by `video_id` — if cards for that video already exist, it skips creation and reports the existing cards.
|
||||
|
||||
**Step 6:** Present questions one-by-one using the same free-text grading flow:
|
||||
1. Show "Question 1/5: ..." and wait for the user's answer. Never include the answer or any hint about revealing it.
|
||||
2. Wait for the user to answer in their own words
|
||||
3. Grade their answer using the grading prompt (see "Reviewing Due Cards" section)
|
||||
4. **IMPORTANT: You MUST reply to the user with feedback before doing anything else.** Show the grade, the correct answer, and when the card is next due. Do NOT silently skip to the next question. Keep it short and plain-text. Example: "Not quite. Answer: {answer}. Next review tomorrow."
|
||||
5. **After showing feedback**, call the rate command and then show the next question in the same message:
|
||||
```bash
|
||||
python3 ~/.hermes/skills/productivity/memento-flashcards/scripts/memento_cards.py rate \
|
||||
--id CARD_ID --rating easy --user-answer "what the user said"
|
||||
```
|
||||
6. Repeat. Every answer MUST receive visible feedback before the next question.
|
||||
|
||||
### Export/Import CSV
|
||||
|
||||
**Export:**
|
||||
```bash
|
||||
python3 ~/.hermes/skills/productivity/memento-flashcards/scripts/memento_cards.py export \
|
||||
--output ~/flashcards.csv
|
||||
```
|
||||
|
||||
Produces a 3-column CSV: `question,answer,collection` (no header row).
|
||||
|
||||
**Import:**
|
||||
```bash
|
||||
python3 ~/.hermes/skills/productivity/memento-flashcards/scripts/memento_cards.py import \
|
||||
--file ~/flashcards.csv \
|
||||
--collection "Imported"
|
||||
```
|
||||
|
||||
Reads a CSV with columns: question, answer, and optionally collection (column 3). If the collection column is missing, uses the `--collection` argument.
|
||||
|
||||
### Statistics
|
||||
|
||||
```bash
|
||||
python3 ~/.hermes/skills/productivity/memento-flashcards/scripts/memento_cards.py stats
|
||||
```
|
||||
|
||||
Returns JSON with:
|
||||
- `total`: total card count
|
||||
- `learning`: cards in active rotation
|
||||
- `retired`: mastered cards
|
||||
- `due_now`: cards due for review right now
|
||||
- `collections`: breakdown by collection name
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- **Never edit `cards.json` directly** — always use the script subcommands to avoid corruption
|
||||
- **Transcript failures** — some YouTube videos have no English transcript or have transcripts disabled; inform the user and suggest another video
|
||||
- **Optional dependency** — `youtube_quiz.py` needs `youtube-transcript-api`; if missing, tell the user to run `pip install youtube-transcript-api`
|
||||
- **Large imports** — CSV imports with thousands of rows work fine but the JSON output may be verbose; summarize the result for the user
|
||||
- **Video ID extraction** — support both `youtube.com/watch?v=ID` and `youtu.be/ID` URL formats
|
||||
|
||||
## Verification
|
||||
|
||||
Verify the helper scripts directly:
|
||||
|
||||
```bash
|
||||
python3 ~/.hermes/skills/productivity/memento-flashcards/scripts/memento_cards.py stats
|
||||
python3 ~/.hermes/skills/productivity/memento-flashcards/scripts/memento_cards.py add --question "Capital of France?" --answer "Paris" --collection "General"
|
||||
python3 ~/.hermes/skills/productivity/memento-flashcards/scripts/memento_cards.py due
|
||||
```
|
||||
|
||||
If you are testing from the repo checkout, run:
|
||||
|
||||
```bash
|
||||
pytest tests/skills/test_memento_cards.py tests/skills/test_youtube_quiz.py -q
|
||||
```
|
||||
|
||||
Agent-level verification:
|
||||
- Start a review and confirm feedback is plain text, brief, and always includes the correct answer before the next card
|
||||
- Run a YouTube quiz flow and confirm each answer receives visible feedback before the next question
|
||||
|
|
@ -0,0 +1,304 @@
|
|||
---
|
||||
title: "Siyuan"
|
||||
sidebar_label: "Siyuan"
|
||||
description: "SiYuan Note API for searching, reading, creating, and managing blocks and documents in a self-hosted knowledge base via curl"
|
||||
---
|
||||
|
||||
{/* This page is auto-generated from the skill's SKILL.md by website/scripts/generate-skill-docs.py. Edit the source SKILL.md, not this page. */}
|
||||
|
||||
# Siyuan
|
||||
|
||||
SiYuan Note API for searching, reading, creating, and managing blocks and documents in a self-hosted knowledge base via curl.
|
||||
|
||||
## Skill metadata
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| Source | Optional — install with `hermes skills install official/productivity/siyuan` |
|
||||
| Path | `optional-skills/productivity/siyuan` |
|
||||
| Version | `1.0.0` |
|
||||
| Author | FEUAZUR |
|
||||
| License | MIT |
|
||||
| Tags | `SiYuan`, `Notes`, `Knowledge Base`, `PKM`, `API` |
|
||||
| Related skills | [`obsidian`](/docs/user-guide/skills/bundled/note-taking/note-taking-obsidian), [`notion`](/docs/user-guide/skills/bundled/productivity/productivity-notion) |
|
||||
|
||||
## Reference: full SKILL.md
|
||||
|
||||
:::info
|
||||
The following is the complete skill definition that Hermes loads when this skill is triggered. This is what the agent sees as instructions when the skill is active.
|
||||
:::
|
||||
|
||||
# SiYuan Note API
|
||||
|
||||
Use the [SiYuan](https://github.com/siyuan-note/siyuan) kernel API via curl to search, read, create, update, and delete blocks and documents in a self-hosted knowledge base. No extra tools needed -- just curl and an API token.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. Install and run SiYuan (desktop or Docker)
|
||||
2. Get your API token: **Settings > About > API token**
|
||||
3. Store it in `~/.hermes/.env`:
|
||||
```
|
||||
SIYUAN_TOKEN=your_token_here
|
||||
SIYUAN_URL=http://127.0.0.1:6806
|
||||
```
|
||||
`SIYUAN_URL` defaults to `http://127.0.0.1:6806` if not set.
|
||||
|
||||
## API Basics
|
||||
|
||||
All SiYuan API calls are **POST with JSON body**. Every request follows this pattern:
|
||||
|
||||
```bash
|
||||
curl -s -X POST "${SIYUAN_URL:-http://127.0.0.1:6806}/api/..." \
|
||||
-H "Authorization: Token $SIYUAN_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"param": "value"}'
|
||||
```
|
||||
|
||||
Responses are JSON with this structure:
|
||||
```json
|
||||
{"code": 0, "msg": "", "data": { ... }}
|
||||
```
|
||||
`code: 0` means success. Any other value is an error -- check `msg` for details.
|
||||
|
||||
**ID format:** SiYuan IDs look like `20210808180117-6v0mkxr` (14-digit timestamp + 7 alphanumeric chars).
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Operation | Endpoint |
|
||||
|-----------|----------|
|
||||
| Full-text search | `/api/search/fullTextSearchBlock` |
|
||||
| SQL query | `/api/query/sql` |
|
||||
| Read block | `/api/block/getBlockKramdown` |
|
||||
| Read children | `/api/block/getChildBlocks` |
|
||||
| Get path | `/api/filetree/getHPathByID` |
|
||||
| Get attributes | `/api/attr/getBlockAttrs` |
|
||||
| List notebooks | `/api/notebook/lsNotebooks` |
|
||||
| List documents | `/api/filetree/listDocsByPath` |
|
||||
| Create notebook | `/api/notebook/createNotebook` |
|
||||
| Create document | `/api/filetree/createDocWithMd` |
|
||||
| Append block | `/api/block/appendBlock` |
|
||||
| Update block | `/api/block/updateBlock` |
|
||||
| Rename document | `/api/filetree/renameDocByID` |
|
||||
| Set attributes | `/api/attr/setBlockAttrs` |
|
||||
| Delete block | `/api/block/deleteBlock` |
|
||||
| Delete document | `/api/filetree/removeDocByID` |
|
||||
| Export as Markdown | `/api/export/exportMdContent` |
|
||||
|
||||
## Common Operations
|
||||
|
||||
### Search (Full-Text)
|
||||
|
||||
```bash
|
||||
curl -s -X POST "${SIYUAN_URL:-http://127.0.0.1:6806}/api/search/fullTextSearchBlock" \
|
||||
-H "Authorization: Token $SIYUAN_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"query": "meeting notes", "page": 0}' | jq '.data.blocks[:5]'
|
||||
```
|
||||
|
||||
### Search (SQL)
|
||||
|
||||
Query the blocks database directly. Only SELECT statements are safe.
|
||||
|
||||
```bash
|
||||
curl -s -X POST "${SIYUAN_URL:-http://127.0.0.1:6806}/api/query/sql" \
|
||||
-H "Authorization: Token $SIYUAN_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"stmt": "SELECT id, content, type, box FROM blocks WHERE content LIKE '\''%keyword%'\'' AND type='\''p'\'' LIMIT 20"}' | jq '.data'
|
||||
```
|
||||
|
||||
Useful columns: `id`, `parent_id`, `root_id`, `box` (notebook ID), `path`, `content`, `type`, `subtype`, `created`, `updated`.
|
||||
|
||||
### Read Block Content
|
||||
|
||||
Returns block content in Kramdown (Markdown-like) format.
|
||||
|
||||
```bash
|
||||
curl -s -X POST "${SIYUAN_URL:-http://127.0.0.1:6806}/api/block/getBlockKramdown" \
|
||||
-H "Authorization: Token $SIYUAN_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"id": "20210808180117-6v0mkxr"}' | jq '.data.kramdown'
|
||||
```
|
||||
|
||||
### Read Child Blocks
|
||||
|
||||
```bash
|
||||
curl -s -X POST "${SIYUAN_URL:-http://127.0.0.1:6806}/api/block/getChildBlocks" \
|
||||
-H "Authorization: Token $SIYUAN_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"id": "20210808180117-6v0mkxr"}' | jq '.data'
|
||||
```
|
||||
|
||||
### Get Human-Readable Path
|
||||
|
||||
```bash
|
||||
curl -s -X POST "${SIYUAN_URL:-http://127.0.0.1:6806}/api/filetree/getHPathByID" \
|
||||
-H "Authorization: Token $SIYUAN_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"id": "20210808180117-6v0mkxr"}' | jq '.data'
|
||||
```
|
||||
|
||||
### Get Block Attributes
|
||||
|
||||
```bash
|
||||
curl -s -X POST "${SIYUAN_URL:-http://127.0.0.1:6806}/api/attr/getBlockAttrs" \
|
||||
-H "Authorization: Token $SIYUAN_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"id": "20210808180117-6v0mkxr"}' | jq '.data'
|
||||
```
|
||||
|
||||
### List Notebooks
|
||||
|
||||
```bash
|
||||
curl -s -X POST "${SIYUAN_URL:-http://127.0.0.1:6806}/api/notebook/lsNotebooks" \
|
||||
-H "Authorization: Token $SIYUAN_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{}' | jq '.data.notebooks[] | {id, name, closed}'
|
||||
```
|
||||
|
||||
### List Documents in a Notebook
|
||||
|
||||
```bash
|
||||
curl -s -X POST "${SIYUAN_URL:-http://127.0.0.1:6806}/api/filetree/listDocsByPath" \
|
||||
-H "Authorization: Token $SIYUAN_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"notebook": "NOTEBOOK_ID", "path": "/"}' | jq '.data.files[] | {id, name}'
|
||||
```
|
||||
|
||||
### Create a Document
|
||||
|
||||
```bash
|
||||
curl -s -X POST "${SIYUAN_URL:-http://127.0.0.1:6806}/api/filetree/createDocWithMd" \
|
||||
-H "Authorization: Token $SIYUAN_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"notebook": "NOTEBOOK_ID",
|
||||
"path": "/Meeting Notes/2026-03-22",
|
||||
"markdown": "# Meeting Notes\n\n- Discussed project timeline\n- Assigned tasks"
|
||||
}' | jq '.data'
|
||||
```
|
||||
|
||||
### Create a Notebook
|
||||
|
||||
```bash
|
||||
curl -s -X POST "${SIYUAN_URL:-http://127.0.0.1:6806}/api/notebook/createNotebook" \
|
||||
-H "Authorization: Token $SIYUAN_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name": "My New Notebook"}' | jq '.data.notebook.id'
|
||||
```
|
||||
|
||||
### Append Block to Document
|
||||
|
||||
```bash
|
||||
curl -s -X POST "${SIYUAN_URL:-http://127.0.0.1:6806}/api/block/appendBlock" \
|
||||
-H "Authorization: Token $SIYUAN_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"parentID": "DOCUMENT_OR_BLOCK_ID",
|
||||
"data": "New paragraph added at the end.",
|
||||
"dataType": "markdown"
|
||||
}' | jq '.data'
|
||||
```
|
||||
|
||||
Also available: `/api/block/prependBlock` (same params, inserts at the beginning) and `/api/block/insertBlock` (uses `previousID` instead of `parentID` to insert after a specific block).
|
||||
|
||||
### Update Block Content
|
||||
|
||||
```bash
|
||||
curl -s -X POST "${SIYUAN_URL:-http://127.0.0.1:6806}/api/block/updateBlock" \
|
||||
-H "Authorization: Token $SIYUAN_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"id": "BLOCK_ID",
|
||||
"data": "Updated content here.",
|
||||
"dataType": "markdown"
|
||||
}' | jq '.data'
|
||||
```
|
||||
|
||||
### Rename a Document
|
||||
|
||||
```bash
|
||||
curl -s -X POST "${SIYUAN_URL:-http://127.0.0.1:6806}/api/filetree/renameDocByID" \
|
||||
-H "Authorization: Token $SIYUAN_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"id": "DOCUMENT_ID", "title": "New Title"}'
|
||||
```
|
||||
|
||||
### Set Block Attributes
|
||||
|
||||
Custom attributes must be prefixed with `custom-`:
|
||||
|
||||
```bash
|
||||
curl -s -X POST "${SIYUAN_URL:-http://127.0.0.1:6806}/api/attr/setBlockAttrs" \
|
||||
-H "Authorization: Token $SIYUAN_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"id": "BLOCK_ID",
|
||||
"attrs": {
|
||||
"custom-status": "reviewed",
|
||||
"custom-priority": "high"
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
### Delete a Block
|
||||
|
||||
```bash
|
||||
curl -s -X POST "${SIYUAN_URL:-http://127.0.0.1:6806}/api/block/deleteBlock" \
|
||||
-H "Authorization: Token $SIYUAN_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"id": "BLOCK_ID"}'
|
||||
```
|
||||
|
||||
To delete a whole document: use `/api/filetree/removeDocByID` with `{"id": "DOC_ID"}`.
|
||||
To delete a notebook: use `/api/notebook/removeNotebook` with `{"notebook": "NOTEBOOK_ID"}`.
|
||||
|
||||
### Export Document as Markdown
|
||||
|
||||
```bash
|
||||
curl -s -X POST "${SIYUAN_URL:-http://127.0.0.1:6806}/api/export/exportMdContent" \
|
||||
-H "Authorization: Token $SIYUAN_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"id": "DOCUMENT_ID"}' | jq -r '.data.content'
|
||||
```
|
||||
|
||||
## Block Types
|
||||
|
||||
Common `type` values in SQL queries:
|
||||
|
||||
| Type | Description |
|
||||
|------|-------------|
|
||||
| `d` | Document (root block) |
|
||||
| `p` | Paragraph |
|
||||
| `h` | Heading |
|
||||
| `l` | List |
|
||||
| `i` | List item |
|
||||
| `c` | Code block |
|
||||
| `m` | Math block |
|
||||
| `t` | Table |
|
||||
| `b` | Blockquote |
|
||||
| `s` | Super block |
|
||||
| `html` | HTML block |
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- **All endpoints are POST** -- even read-only operations. Do not use GET.
|
||||
- **SQL safety**: only use SELECT queries. INSERT/UPDATE/DELETE/DROP are dangerous and should never be sent.
|
||||
- **ID validation**: IDs match the pattern `YYYYMMDDHHmmss-xxxxxxx`. Reject anything else.
|
||||
- **Error responses**: always check `code != 0` in responses before processing `data`.
|
||||
- **Large documents**: block content and export results can be very large. Use `LIMIT` in SQL and pipe through `jq` to extract only what you need.
|
||||
- **Notebook IDs**: when working with a specific notebook, get its ID first via `lsNotebooks`.
|
||||
|
||||
## Alternative: MCP Server
|
||||
|
||||
If you prefer a native integration instead of curl, install the SiYuan MCP server:
|
||||
|
||||
```yaml
|
||||
# In ~/.hermes/config.yaml under mcp_servers:
|
||||
mcp_servers:
|
||||
siyuan:
|
||||
command: npx
|
||||
args: ["-y", "@porkll/siyuan-mcp"]
|
||||
env:
|
||||
SIYUAN_TOKEN: "your_token"
|
||||
SIYUAN_URL: "http://127.0.0.1:6806"
|
||||
```
|
||||
|
|
@ -0,0 +1,434 @@
|
|||
---
|
||||
title: "Telephony — Give Hermes phone capabilities without core tool changes"
|
||||
sidebar_label: "Telephony"
|
||||
description: "Give Hermes phone capabilities without core tool changes"
|
||||
---
|
||||
|
||||
{/* This page is auto-generated from the skill's SKILL.md by website/scripts/generate-skill-docs.py. Edit the source SKILL.md, not this page. */}
|
||||
|
||||
# Telephony
|
||||
|
||||
Give Hermes phone capabilities without core tool changes. Provision and persist a Twilio number, send and receive SMS/MMS, make direct calls, and place AI-driven outbound calls through Bland.ai or Vapi.
|
||||
|
||||
## Skill metadata
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| Source | Optional — install with `hermes skills install official/productivity/telephony` |
|
||||
| Path | `optional-skills/productivity/telephony` |
|
||||
| Version | `1.0.0` |
|
||||
| Author | Nous Research |
|
||||
| License | MIT |
|
||||
| Tags | `telephony`, `phone`, `sms`, `mms`, `voice`, `twilio`, `bland.ai`, `vapi`, `calling`, `texting` |
|
||||
| Related skills | [`maps`](/docs/user-guide/skills/bundled/productivity/productivity-maps), [`google-workspace`](/docs/user-guide/skills/bundled/productivity/productivity-google-workspace), [`agentmail`](/docs/user-guide/skills/optional/email/email-agentmail) |
|
||||
|
||||
## Reference: full SKILL.md
|
||||
|
||||
:::info
|
||||
The following is the complete skill definition that Hermes loads when this skill is triggered. This is what the agent sees as instructions when the skill is active.
|
||||
:::
|
||||
|
||||
# Telephony — Numbers, Calls, and Texts without Core Tool Changes
|
||||
|
||||
This optional skill gives Hermes practical phone capabilities while keeping telephony out of the core tool list.
|
||||
|
||||
It ships with a helper script, `scripts/telephony.py`, that can:
|
||||
- save provider credentials into `~/.hermes/.env`
|
||||
- search for and buy a Twilio phone number
|
||||
- remember that owned number for later sessions
|
||||
- send SMS / MMS from the owned number
|
||||
- poll inbound SMS for that number with no webhook server required
|
||||
- make direct Twilio calls using TwiML `<Say>` or `<Play>`
|
||||
- import the owned Twilio number into Vapi
|
||||
- place outbound AI calls through Bland.ai or Vapi
|
||||
|
||||
## What this solves
|
||||
|
||||
This skill is meant to cover the practical phone tasks users actually want:
|
||||
- outbound calls
|
||||
- texting
|
||||
- owning a reusable agent number
|
||||
- checking messages that arrive to that number later
|
||||
- preserving that number and related IDs between sessions
|
||||
- future-friendly telephony identity for inbound SMS polling and other automations
|
||||
|
||||
It does **not** turn Hermes into a real-time inbound phone gateway. Inbound SMS is handled by polling the Twilio REST API. That is enough for many workflows, including notifications and some one-time-code retrieval, without adding core webhook infrastructure.
|
||||
|
||||
## Safety rules — mandatory
|
||||
|
||||
1. Always confirm before placing a call or sending a text.
|
||||
2. Never dial emergency numbers.
|
||||
3. Never use telephony for harassment, spam, impersonation, or anything illegal.
|
||||
4. Treat third-party phone numbers as sensitive operational data:
|
||||
- do not save them to Hermes memory
|
||||
- do not include them in skill docs, summaries, or follow-up notes unless the user explicitly wants that
|
||||
5. It is fine to persist the **agent-owned Twilio number** because that is part of the user's configuration.
|
||||
6. VoIP numbers are **not guaranteed** to work for all third-party 2FA flows. Use with caution and set user expectations clearly.
|
||||
|
||||
## Decision tree — which service to use?
|
||||
|
||||
Use this logic instead of hardcoded provider routing:
|
||||
|
||||
### 1) "I want Hermes to own a real phone number"
|
||||
Use **Twilio**.
|
||||
|
||||
Why:
|
||||
- easiest path to buying and keeping a number
|
||||
- best SMS / MMS support
|
||||
- simplest inbound SMS polling story
|
||||
- cleanest future path to inbound webhooks or call handling
|
||||
|
||||
Use cases:
|
||||
- receive texts later
|
||||
- send deployment alerts / cron notifications
|
||||
- maintain a reusable phone identity for the agent
|
||||
- experiment with phone-based auth flows later
|
||||
|
||||
### 2) "I only need the easiest outbound AI phone call right now"
|
||||
Use **Bland.ai**.
|
||||
|
||||
Why:
|
||||
- quickest setup
|
||||
- one API key
|
||||
- no need to first buy/import a number yourself
|
||||
|
||||
Tradeoff:
|
||||
- less flexible
|
||||
- voice quality is decent, but not the best
|
||||
|
||||
### 3) "I want the best conversational AI voice quality"
|
||||
Use **Twilio + Vapi**.
|
||||
|
||||
Why:
|
||||
- Twilio gives you the owned number
|
||||
- Vapi gives you better conversational AI call quality and more voice/model flexibility
|
||||
|
||||
Recommended flow:
|
||||
1. Buy/save a Twilio number
|
||||
2. Import it into Vapi
|
||||
3. Save the returned `VAPI_PHONE_NUMBER_ID`
|
||||
4. Use `ai-call --provider vapi`
|
||||
|
||||
### 4) "I want to call with a custom prerecorded voice message"
|
||||
Use **Twilio direct call** with a public audio URL.
|
||||
|
||||
Why:
|
||||
- easiest way to play a custom MP3
|
||||
- pairs well with Hermes `text_to_speech` plus a public file host or tunnel
|
||||
|
||||
## Files and persistent state
|
||||
|
||||
The skill persists telephony state in two places:
|
||||
|
||||
### `~/.hermes/.env`
|
||||
Used for long-lived provider credentials and owned-number IDs, for example:
|
||||
- `TWILIO_ACCOUNT_SID`
|
||||
- `TWILIO_AUTH_TOKEN`
|
||||
- `TWILIO_PHONE_NUMBER`
|
||||
- `TWILIO_PHONE_NUMBER_SID`
|
||||
- `BLAND_API_KEY`
|
||||
- `VAPI_API_KEY`
|
||||
- `VAPI_PHONE_NUMBER_ID`
|
||||
- `PHONE_PROVIDER` (AI call provider: bland or vapi)
|
||||
|
||||
### `~/.hermes/telephony_state.json`
|
||||
Used for skill-only state that should survive across sessions, for example:
|
||||
- remembered default Twilio number / SID
|
||||
- remembered Vapi phone number ID
|
||||
- last inbound message SID/date for inbox polling checkpoints
|
||||
|
||||
This means:
|
||||
- the next time the skill is loaded, `diagnose` can tell you what number is already configured
|
||||
- `twilio-inbox --since-last --mark-seen` can continue from the previous checkpoint
|
||||
|
||||
## Locate the helper script
|
||||
|
||||
After installing this skill, locate the script like this:
|
||||
|
||||
```bash
|
||||
SCRIPT="$(find ~/.hermes/skills -path '*/telephony/scripts/telephony.py' -print -quit)"
|
||||
```
|
||||
|
||||
If `SCRIPT` is empty, the skill is not installed yet.
|
||||
|
||||
## Install
|
||||
|
||||
This is an official optional skill, so install it from the Skills Hub:
|
||||
|
||||
```bash
|
||||
hermes skills search telephony
|
||||
hermes skills install official/productivity/telephony
|
||||
```
|
||||
|
||||
## Provider setup
|
||||
|
||||
### Twilio — owned number, SMS/MMS, direct calls, inbound SMS polling
|
||||
|
||||
Sign up at:
|
||||
- https://www.twilio.com/try-twilio
|
||||
|
||||
Then save credentials into Hermes:
|
||||
|
||||
```bash
|
||||
python3 "$SCRIPT" save-twilio ACXXXXXXXXXXXXXXXXXXXXXXXXXXXX your_auth_token_here
|
||||
```
|
||||
|
||||
Search for available numbers:
|
||||
|
||||
```bash
|
||||
python3 "$SCRIPT" twilio-search --country US --area-code 702 --limit 5
|
||||
```
|
||||
|
||||
Buy and remember a number:
|
||||
|
||||
```bash
|
||||
python3 "$SCRIPT" twilio-buy "+17025551234" --save-env
|
||||
```
|
||||
|
||||
List owned numbers:
|
||||
|
||||
```bash
|
||||
python3 "$SCRIPT" twilio-owned
|
||||
```
|
||||
|
||||
Set one of them as the default later:
|
||||
|
||||
```bash
|
||||
python3 "$SCRIPT" twilio-set-default "+17025551234" --save-env
|
||||
# or
|
||||
python3 "$SCRIPT" twilio-set-default PNXXXXXXXXXXXXXXXXXXXXXXXXXXXX --save-env
|
||||
```
|
||||
|
||||
### Bland.ai — easiest outbound AI calling
|
||||
|
||||
Sign up at:
|
||||
- https://app.bland.ai
|
||||
|
||||
Save config:
|
||||
|
||||
```bash
|
||||
python3 "$SCRIPT" save-bland your_bland_api_key --voice mason
|
||||
```
|
||||
|
||||
### Vapi — better conversational voice quality
|
||||
|
||||
Sign up at:
|
||||
- https://dashboard.vapi.ai
|
||||
|
||||
Save the API key first:
|
||||
|
||||
```bash
|
||||
python3 "$SCRIPT" save-vapi your_vapi_api_key
|
||||
```
|
||||
|
||||
Import your owned Twilio number into Vapi and persist the returned phone number ID:
|
||||
|
||||
```bash
|
||||
python3 "$SCRIPT" vapi-import-twilio --save-env
|
||||
```
|
||||
|
||||
If you already know the Vapi phone number ID, save it directly:
|
||||
|
||||
```bash
|
||||
python3 "$SCRIPT" save-vapi your_vapi_api_key --phone-number-id vapi_phone_number_id_here
|
||||
```
|
||||
|
||||
## Diagnose current state
|
||||
|
||||
At any time, inspect what the skill already knows:
|
||||
|
||||
```bash
|
||||
python3 "$SCRIPT" diagnose
|
||||
```
|
||||
|
||||
Use this first when resuming work in a later session.
|
||||
|
||||
## Common workflows
|
||||
|
||||
### A. Buy an agent number and keep using it later
|
||||
|
||||
1. Save Twilio credentials:
|
||||
```bash
|
||||
python3 "$SCRIPT" save-twilio AC... auth_token_here
|
||||
```
|
||||
|
||||
2. Search for a number:
|
||||
```bash
|
||||
python3 "$SCRIPT" twilio-search --country US --area-code 702 --limit 10
|
||||
```
|
||||
|
||||
3. Buy it and save it into `~/.hermes/.env` + state:
|
||||
```bash
|
||||
python3 "$SCRIPT" twilio-buy "+17025551234" --save-env
|
||||
```
|
||||
|
||||
4. Next session, run:
|
||||
```bash
|
||||
python3 "$SCRIPT" diagnose
|
||||
```
|
||||
This shows the remembered default number and inbox checkpoint state.
|
||||
|
||||
### B. Send a text from the agent number
|
||||
|
||||
```bash
|
||||
python3 "$SCRIPT" twilio-send-sms "+15551230000" "Your deployment completed successfully."
|
||||
```
|
||||
|
||||
With media:
|
||||
|
||||
```bash
|
||||
python3 "$SCRIPT" twilio-send-sms "+15551230000" "Here is the chart." --media-url "https://example.com/chart.png"
|
||||
```
|
||||
|
||||
### C. Check inbound texts later with no webhook server
|
||||
|
||||
Poll the inbox for the default Twilio number:
|
||||
|
||||
```bash
|
||||
python3 "$SCRIPT" twilio-inbox --limit 20
|
||||
```
|
||||
|
||||
Only show messages that arrived after the last checkpoint, and advance the checkpoint when you're done reading:
|
||||
|
||||
```bash
|
||||
python3 "$SCRIPT" twilio-inbox --since-last --mark-seen
|
||||
```
|
||||
|
||||
This is the main answer to “how do I access messages the number receives next time the skill is loaded?”
|
||||
|
||||
### D. Make a direct Twilio call with built-in TTS
|
||||
|
||||
```bash
|
||||
python3 "$SCRIPT" twilio-call "+15551230000" --message "Hello! This is Hermes calling with your status update." --voice Polly.Joanna
|
||||
```
|
||||
|
||||
### E. Call with a prerecorded / custom voice message
|
||||
|
||||
This is the main path for reusing Hermes's existing `text_to_speech` support.
|
||||
|
||||
Use this when:
|
||||
- you want the call to use Hermes's configured TTS voice rather than Twilio `<Say>`
|
||||
- you want a one-way voice delivery (briefing, alert, joke, reminder, status update)
|
||||
- you do **not** need a live conversational phone call
|
||||
|
||||
Generate or host audio separately, then:
|
||||
|
||||
```bash
|
||||
python3 "$SCRIPT" twilio-call "+155****0000" --audio-url "https://example.com/briefing.mp3"
|
||||
```
|
||||
|
||||
Recommended Hermes TTS -> Twilio Play workflow:
|
||||
|
||||
1. Generate the audio with Hermes `text_to_speech`.
|
||||
2. Make the resulting MP3 publicly reachable.
|
||||
3. Place the Twilio call with `--audio-url`.
|
||||
|
||||
Example agent flow:
|
||||
- Ask Hermes to create the message audio with `text_to_speech`
|
||||
- If needed, expose the file with a temporary static host / tunnel / object storage URL
|
||||
- Use `twilio-call --audio-url ...` to deliver it by phone
|
||||
|
||||
Good hosting options for the MP3:
|
||||
- a temporary public object/storage URL
|
||||
- a short-lived tunnel to a local static file server
|
||||
- any existing HTTPS URL the phone provider can fetch directly
|
||||
|
||||
Important note:
|
||||
- Hermes TTS is great for prerecorded outbound messages
|
||||
- Bland/Vapi are better for **live conversational AI calls** because they handle the real-time telephony audio stack themselves
|
||||
- Hermes STT/TTS alone is not being used here as a full duplex phone conversation engine; that would require a much heavier streaming/webhook integration than this skill is trying to introduce
|
||||
|
||||
### F. Navigate a phone tree / IVR with Twilio direct calling
|
||||
|
||||
If you need to press digits after the call connects, use `--send-digits`.
|
||||
Twilio interprets `w` as a short wait.
|
||||
|
||||
```bash
|
||||
python3 "$SCRIPT" twilio-call "+18005551234" --message "Connecting to billing now." --send-digits "ww1w2w3"
|
||||
```
|
||||
|
||||
This is useful for reaching a specific menu branch before handing off to a human or delivering a short status message.
|
||||
|
||||
### G. Outbound AI phone call with Bland.ai
|
||||
|
||||
```bash
|
||||
python3 "$SCRIPT" ai-call "+15551230000" "Call the dental office, ask for a cleaning appointment on Tuesday afternoon, and if they do not have Tuesday availability, ask for Wednesday or Thursday instead." --provider bland --voice mason --max-duration 3
|
||||
```
|
||||
|
||||
Check status:
|
||||
|
||||
```bash
|
||||
python3 "$SCRIPT" ai-status <call_id> --provider bland
|
||||
```
|
||||
|
||||
Ask Bland analysis questions after completion:
|
||||
|
||||
```bash
|
||||
python3 "$SCRIPT" ai-status <call_id> --provider bland --analyze "Was the appointment confirmed?,What date and time?,Any special instructions?"
|
||||
```
|
||||
|
||||
### H. Outbound AI phone call with Vapi on your owned number
|
||||
|
||||
1. Import your Twilio number into Vapi:
|
||||
```bash
|
||||
python3 "$SCRIPT" vapi-import-twilio --save-env
|
||||
```
|
||||
|
||||
2. Place the call:
|
||||
```bash
|
||||
python3 "$SCRIPT" ai-call "+15551230000" "You are calling to make a dinner reservation for two at 7:30 PM. If that is unavailable, ask for the nearest time between 6:30 and 8:30 PM." --provider vapi --max-duration 4
|
||||
```
|
||||
|
||||
3. Check result:
|
||||
```bash
|
||||
python3 "$SCRIPT" ai-status <call_id> --provider vapi
|
||||
```
|
||||
|
||||
## Suggested agent procedure
|
||||
|
||||
When the user asks for a call or text:
|
||||
|
||||
1. Determine which path fits the request via the decision tree.
|
||||
2. Run `diagnose` if configuration state is unclear.
|
||||
3. Gather the full task details.
|
||||
4. Confirm with the user before dialing or texting.
|
||||
5. Use the correct command.
|
||||
6. Poll for results if needed.
|
||||
7. Summarize the outcome without persisting third-party numbers to Hermes memory.
|
||||
|
||||
## What this skill still does not do
|
||||
|
||||
- real-time inbound call answering
|
||||
- webhook-based live SMS push into the agent loop
|
||||
- guaranteed support for arbitrary third-party 2FA providers
|
||||
|
||||
Those would require more infrastructure than a pure optional skill.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Twilio trial accounts and regional rules can restrict who you can call/text.
|
||||
- Some services reject VoIP numbers for 2FA.
|
||||
- `twilio-inbox` polls the REST API; it is not instant push delivery.
|
||||
- Vapi outbound calling still depends on having a valid imported number.
|
||||
- Bland is easiest, but not always the best-sounding.
|
||||
- Do not store arbitrary third-party phone numbers in Hermes memory.
|
||||
|
||||
## Verification checklist
|
||||
|
||||
After setup, you should be able to do all of the following with just this skill:
|
||||
|
||||
1. `diagnose` shows provider readiness and remembered state
|
||||
2. search and buy a Twilio number
|
||||
3. persist that number to `~/.hermes/.env`
|
||||
4. send an SMS from the owned number
|
||||
5. poll inbound texts for the owned number later
|
||||
6. place a direct Twilio call
|
||||
7. place an AI call via Bland or Vapi
|
||||
|
||||
## References
|
||||
|
||||
- Twilio phone numbers: https://www.twilio.com/docs/phone-numbers/api
|
||||
- Twilio messaging: https://www.twilio.com/docs/messaging/api/message-resource
|
||||
- Twilio voice: https://www.twilio.com/docs/voice/api/call-resource
|
||||
- Vapi docs: https://docs.vapi.ai/
|
||||
- Bland.ai: https://app.bland.ai/
|
||||
Loading…
Add table
Add a link
Reference in a new issue