Compare commits

..

2 Commits

8 changed files with 682 additions and 3 deletions

View File

@ -0,0 +1,80 @@
---
name: himalaya
description: "Himalaya CLI for IMAP/SMTP mail: list, read, search, compose, reply, forward, copy, move, delete."
homepage: https://github.com/pimalaya/himalaya
metadata:
{
"openclaw":
{
"emoji": "📧",
"requires": { "bins": ["himalaya"] },
"install":
[
{
"id": "brew",
"kind": "brew",
"formula": "himalaya",
"bins": ["himalaya"],
"label": "Install Himalaya (brew)",
},
],
},
}
---
# Himalaya
Use `himalaya` for IMAP/SMTP email from shell.
## References
- `references/configuration.md`: account config, auth, backend setup.
- `references/message-composition.md`: MML compose syntax.
## Setup
```bash
himalaya --version
himalaya account configure
```
Config path: `~/.config/himalaya/config.toml`.
Prefer password managers/keyrings for credentials; do not paste secrets into chat/logs.
## Read/search
```bash
himalaya folder list
himalaya envelope list
himalaya message read <id>
himalaya envelope list from alice@example.com subject invoice
```
## Write
```bash
himalaya message write
himalaya template write
himalaya template send < /tmp/message.txt
himalaya message reply <id>
himalaya message forward <id>
```
Use MML for attachments and rich messages; read `references/message-composition.md` first.
## Organize
```bash
himalaya message copy <id> <folder>
himalaya message move <id> <folder>
himalaya message delete <id>
himalaya flag add <id> --flag seen
himalaya flag remove <id> --flag seen
```
## Safety
- Confirm before sending, deleting, or moving many messages.
- Use `--account` when multiple accounts exist.
- Quote exact message IDs in summaries.

View File

@ -0,0 +1,184 @@
# Himalaya Configuration Reference
Configuration file location: `~/.config/himalaya/config.toml`
## Minimal IMAP + SMTP Setup
```toml
[accounts.default]
email = "user@example.com"
display-name = "Your Name"
default = true
# IMAP backend for reading emails
backend.type = "imap"
backend.host = "imap.example.com"
backend.port = 993
backend.encryption.type = "tls"
backend.login = "user@example.com"
backend.auth.type = "password"
backend.auth.raw = "your-password"
# SMTP backend for sending emails
message.send.backend.type = "smtp"
message.send.backend.host = "smtp.example.com"
message.send.backend.port = 587
message.send.backend.encryption.type = "start-tls"
message.send.backend.login = "user@example.com"
message.send.backend.auth.type = "password"
message.send.backend.auth.raw = "your-password"
```
## Password Options
### Raw password (testing only, not recommended)
```toml
backend.auth.raw = "your-password"
```
### Password from command (recommended)
```toml
backend.auth.cmd = "pass show email/imap"
# backend.auth.cmd = "security find-generic-password -a user@example.com -s imap -w"
```
### System keyring (requires keyring feature)
```toml
backend.auth.keyring = "imap-example"
```
Then run `himalaya account configure <account>` to store the password.
## Gmail Configuration
```toml
[accounts.gmail]
email = "you@gmail.com"
display-name = "Your Name"
default = true
backend.type = "imap"
backend.host = "imap.gmail.com"
backend.port = 993
backend.encryption.type = "tls"
backend.login = "you@gmail.com"
backend.auth.type = "password"
backend.auth.cmd = "pass show google/app-password"
message.send.backend.type = "smtp"
message.send.backend.host = "smtp.gmail.com"
message.send.backend.port = 587
message.send.backend.encryption.type = "start-tls"
message.send.backend.login = "you@gmail.com"
message.send.backend.auth.type = "password"
message.send.backend.auth.cmd = "pass show google/app-password"
```
**Note:** Gmail requires an App Password if 2FA is enabled.
## iCloud Configuration
```toml
[accounts.icloud]
email = "you@icloud.com"
display-name = "Your Name"
backend.type = "imap"
backend.host = "imap.mail.me.com"
backend.port = 993
backend.encryption.type = "tls"
backend.login = "you@icloud.com"
backend.auth.type = "password"
backend.auth.cmd = "pass show icloud/app-password"
message.send.backend.type = "smtp"
message.send.backend.host = "smtp.mail.me.com"
message.send.backend.port = 587
message.send.backend.encryption.type = "start-tls"
message.send.backend.login = "you@icloud.com"
message.send.backend.auth.type = "password"
message.send.backend.auth.cmd = "pass show icloud/app-password"
```
**Note:** Generate an app-specific password at appleid.apple.com
## Folder Aliases
Map custom folder names:
```toml
[accounts.default.folder.alias]
inbox = "INBOX"
sent = "Sent"
drafts = "Drafts"
trash = "Trash"
```
## Multiple Accounts
```toml
[accounts.personal]
email = "personal@example.com"
default = true
# ... backend config ...
[accounts.work]
email = "work@company.com"
# ... backend config ...
```
Switch accounts with `--account`:
```bash
himalaya --account work envelope list
```
## Notmuch Backend (local mail)
```toml
[accounts.local]
email = "user@example.com"
backend.type = "notmuch"
backend.db-path = "~/.mail/.notmuch"
```
## OAuth2 Authentication (for providers that support it)
```toml
backend.auth.type = "oauth2"
backend.auth.client-id = "your-client-id"
backend.auth.client-secret.cmd = "pass show oauth/client-secret"
backend.auth.access-token.cmd = "pass show oauth/access-token"
backend.auth.refresh-token.cmd = "pass show oauth/refresh-token"
backend.auth.auth-url = "https://provider.com/oauth/authorize"
backend.auth.token-url = "https://provider.com/oauth/token"
```
## Additional Options
### Signature
```toml
[accounts.default]
signature = "Best regards,\nYour Name"
signature-delim = "-- \n"
```
### Downloads directory
```toml
[accounts.default]
downloads-dir = "~/Downloads/himalaya"
```
### Editor for composing
Set via environment variable:
```bash
export EDITOR="vim"
```

View File

@ -0,0 +1,199 @@
# Message Composition with MML (MIME Meta Language)
Himalaya uses MML for composing emails. MML is a simple XML-based syntax that compiles to MIME messages.
## Basic Message Structure
An email message is a list of **headers** followed by a **body**, separated by a blank line:
```
From: sender@example.com
To: recipient@example.com
Subject: Hello World
This is the message body.
```
## Headers
Common headers:
- `From`: Sender address
- `To`: Primary recipient(s)
- `Cc`: Carbon copy recipients
- `Bcc`: Blind carbon copy recipients
- `Subject`: Message subject
- `Reply-To`: Address for replies (if different from From)
- `In-Reply-To`: Message ID being replied to
### Address Formats
```
To: user@example.com
To: John Doe <john@example.com>
To: "John Doe" <john@example.com>
To: user1@example.com, user2@example.com, "Jane" <jane@example.com>
```
## Plain Text Body
Simple plain text email:
```
From: alice@localhost
To: bob@localhost
Subject: Plain Text Example
Hello, this is a plain text email.
No special formatting needed.
Best,
Alice
```
## MML for Rich Emails
### Multipart Messages
Alternative text/html parts:
```
From: alice@localhost
To: bob@localhost
Subject: Multipart Example
<#multipart type=alternative>
This is the plain text version.
<#part type=text/html>
<html><body><h1>This is the HTML version</h1></body></html>
<#/multipart>
```
### Attachments
Attach a file:
```
From: alice@localhost
To: bob@localhost
Subject: With Attachment
Here is the document you requested.
<#part filename=/path/to/document.pdf><#/part>
```
Attachment with custom name:
```
<#part filename=/path/to/file.pdf name=report.pdf><#/part>
```
Multiple attachments:
```
<#part filename=/path/to/doc1.pdf><#/part>
<#part filename=/path/to/doc2.pdf><#/part>
```
### Inline Images
Embed an image inline:
```
From: alice@localhost
To: bob@localhost
Subject: Inline Image
<#multipart type=related>
<#part type=text/html>
<html><body>
<p>Check out this image:</p>
<img src="cid:image1">
</body></html>
<#part disposition=inline id=image1 filename=/path/to/image.png><#/part>
<#/multipart>
```
### Mixed Content (Text + Attachments)
```
From: alice@localhost
To: bob@localhost
Subject: Mixed Content
<#multipart type=mixed>
<#part type=text/plain>
Please find the attached files.
Best,
Alice
<#part filename=/path/to/file1.pdf><#/part>
<#part filename=/path/to/file2.zip><#/part>
<#/multipart>
```
## MML Tag Reference
### `<#multipart>`
Groups multiple parts together.
- `type=alternative`: Different representations of same content
- `type=mixed`: Independent parts (text + attachments)
- `type=related`: Parts that reference each other (HTML + images)
### `<#part>`
Defines a message part.
- `type=<mime-type>`: Content type (e.g., `text/html`, `application/pdf`)
- `filename=<path>`: File to attach
- `name=<name>`: Display name for attachment
- `disposition=inline`: Display inline instead of as attachment
- `id=<cid>`: Content ID for referencing in HTML
## Composing from CLI
### Interactive compose
Opens your `$EDITOR`:
```bash
himalaya message write
```
### Reply (opens editor with quoted message)
```bash
himalaya message reply 42
himalaya message reply 42 --all # reply-all
```
### Forward
```bash
himalaya message forward 42
```
### Send from stdin
```bash
cat message.txt | himalaya template send
```
### Prefill headers from CLI
```bash
himalaya message write \
-H "To:recipient@example.com" \
-H "Subject:Quick Message" \
"Message body here"
```
## Tips
- The editor opens with a template; fill in headers and body.
- Save and exit the editor to send; exit without saving to cancel.
- MML parts are compiled to proper MIME when sending.
- Use `himalaya message export --full` to inspect the raw MIME structure of received emails.

View File

@ -0,0 +1,161 @@
---
name: skill-creator
description: Create or update PicoBot skills. Use when the user wants a new skill, wants an existing skill revised, or needs a skill scaffold that follows PicoBot's workspace/skills mechanism.
---
# Skill Creator
Use this skill when building a new PicoBot skill or updating an existing one.
## About Skills
Skills are small, self-contained folders that give PicoBot procedural knowledge for a narrow task.
They are most useful when the same workflow, file format, or domain rule would otherwise be
re-explained over and over.
## PicoBot conventions
- Put `always: true` in the `SKILL.md` frontmatter when the skill should be injected on every turn.
- Prefer `{workspace}/skills/<skill-name>/` for skills created by the agent.
- `~/.picobot/skills/` is the built-in install location.
- `~/.agents/skills/` is the fallback shared location.
- Each skill must have a `SKILL.md` file.
- Add `agents/openai.yaml` when you want UI metadata or a default invocation prompt.
- Use `references/` for detailed docs and `assets/` for templates or bundled files.
- Keep `SKILL.md` concise; move long instructions into references.
- Use `always: true` only when the skill should be injected on every turn.
## Core Principles
### Keep It Concise
Only add information the model actually needs. Prefer short rules and concrete examples over
long explanations. If a rule can be implied from the task, leave it out.
### Set the Right Constraints
- Use high freedom when many approaches are valid.
- Use medium freedom when there is a preferred pattern but some variation is fine.
- Use low freedom when the workflow is fragile and should be followed closely.
### Protect Validation
When a skill changes, verify it against a realistic task path. Check that:
- the skill name is discoverable,
- the frontmatter is valid,
- any bundled references are actually reachable,
- and the instructions still lead to the intended output.
## Skill Anatomy
Every PicoBot skill should have:
- a required `SKILL.md`,
- optional `agents/openai.yaml` for UI metadata,
- optional `references/skill-template.md` as a starting scaffold,
- optional `references/` for longer docs,
- optional `assets/` for templates, icons, or sample files.
## Workflow
### 1) Capture Intent
Start by figuring out what the user actually wants the skill to do.
Check:
- what task the skill should enable,
- when it should trigger,
- what the expected output looks like,
- whether the task is objectively testable,
- whether the skill needs bundled references or assets.
If the request is vague, ask short, focused questions before writing files.
### 2) Draft the Skill
1. Choose a clear skill name and a narrow purpose.
2. Write `SKILL.md` with a trigger-focused `description` and a concise body.
3. Put `always: true` in the frontmatter only when the skill should be injected on every turn.
4. Add `agents/openai.yaml` for UI metadata if the skill should appear in a picker or chip list.
5. Add `references/` or `assets/` only when they reduce repetition.
### 3) Validate the Draft
Before treating the skill as done, check that:
- the frontmatter parses,
- the skill is discoverable through `get_skill`,
- the body is still concise,
- the references and assets are reachable,
- the instructions are specific enough to lead to the right output.
For objective workflows, also test the skill against a small set of representative prompts or files.
### 4) Iterate
Revise the skill based on what you learn.
- Tighten the description if the skill under-triggers.
- Narrow the instructions if the model wanders.
- Add examples if the output format keeps drifting.
- Move details into `references/` if `SKILL.md` starts to grow.
### 5) Optimize Triggering
The `description` is the main trigger surface.
- Make it specific.
- Include the contexts where the skill should be used.
- Prefer wording that helps the model notice the skill even when the user does not name it exactly.
- Keep it concise, but do not make it vague.
### 6) Re-open and Confirm
Use `get_skill action="get"` one more time to confirm the final shape if anything changed materially.
## Progressive Disclosure
Keep `SKILL.md` as the entry point and move detail into separate files when it starts to grow:
- Put setup steps, examples, and edge cases in `references/`.
- Put reusable templates or sample files in `assets/`.
- Link from `SKILL.md` to those files instead of duplicating content.
- Start new skills from `references/skill-template.md` when you want a consistent scaffold.
## What Not To Include
- Do not add general project docs that do not help create the skill.
- Do not dump every possible edge case into `SKILL.md`.
- Do not duplicate the same guidance in both `SKILL.md` and `references/`.
- Do not make the skill broad just because it can handle more cases.
## Skill scaffold
```md
---
name: example-skill
description: What this skill helps with and when to use it.
always: true
---
# Example Skill
Short, direct instructions for the agent.
```
## Good fit
- A reusable workflow with a clear trigger.
- A task that benefits from bundled references or templates.
- A skill that should be easy to discover and update inside PicoBot.
## Practical Rule of Thumb
If the skill is:
- **objective**: add test cases and validate output shape,
- **workflow-heavy**: spell out the sequence and the stop conditions,
- **content-heavy**: move examples and long references into `references/`,
- **trigger-sensitive**: spend more time on `description` than on the body.

View File

@ -0,0 +1,4 @@
interface:
display_name: "Skill Creator"
short_description: "Create or update a PicoBot skill"
default_prompt: "Use $skill-name to scaffold or update a PicoBot skill."

View File

@ -0,0 +1,44 @@
# PicoBot Skill Template
Use this as the starting point for a new skill.
---
name: example-skill
description: What this skill helps with and when to use it.
always: true
---
# Example Skill
## Purpose
Explain what this skill does in one or two sentences.
## When To Use
- Use this skill when ...
- Use this skill when ...
## Workflow
1. Do the first thing.
2. Do the second thing.
3. Verify the result.
## Rules
- Keep the instructions narrow.
- Prefer concrete examples over long explanations.
- Move long details into `references/` or `assets/`.
## Examples
```text
User request -> expected skill behavior
```
## Optional Files
- `agents/openai.yaml` for UI metadata and default prompts.
- `references/` for long-form guidance, edge cases, and examples.
- `assets/` for reusable files, templates, or sample data.

View File

@ -874,8 +874,12 @@ mod tests {
&registry,
);
assert!(matches!(blocks.first(), Some(ContentBlock::Text { text }) if text == "先看这段文字"));
assert!(matches!(blocks.get(1), Some(ContentBlock::Text { text }) if text.contains("用户发来了一个文件")));
assert!(
matches!(blocks.first(), Some(ContentBlock::Text { text }) if text == "先看这段文字")
);
assert!(
matches!(blocks.get(1), Some(ContentBlock::Text { text }) if text.contains("用户发来了一个文件"))
);
}
}

View File

@ -1125,7 +1125,10 @@ impl FeishuChannel {
.get("event_key")
.and_then(|v| v.as_str())
.unwrap_or("unknown");
(format!("[shared calendar event: {}]", event_key), Vec::new())
(
format!("[shared calendar event: {}]", event_key),
Vec::new(),
)
} else {
("[shared calendar event]".to_string(), Vec::new())
}