daemora 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +666 -0
- package/SOUL.md +104 -0
- package/config/hooks.json +14 -0
- package/config/mcp.json +145 -0
- package/package.json +86 -0
- package/skills/.gitkeep +0 -0
- package/skills/apple-notes.md +193 -0
- package/skills/apple-reminders.md +189 -0
- package/skills/camsnap.md +162 -0
- package/skills/coding.md +14 -0
- package/skills/documents.md +13 -0
- package/skills/email.md +13 -0
- package/skills/gif-search.md +196 -0
- package/skills/healthcheck.md +225 -0
- package/skills/image-gen.md +147 -0
- package/skills/model-usage.md +182 -0
- package/skills/obsidian.md +207 -0
- package/skills/pdf.md +211 -0
- package/skills/research.md +13 -0
- package/skills/skill-creator.md +142 -0
- package/skills/spotify.md +149 -0
- package/skills/summarize.md +230 -0
- package/skills/things.md +199 -0
- package/skills/tmux.md +204 -0
- package/skills/trello.md +183 -0
- package/skills/video-frames.md +202 -0
- package/skills/weather.md +127 -0
- package/src/a2a/A2AClient.js +136 -0
- package/src/a2a/A2AServer.js +316 -0
- package/src/a2a/AgentCard.js +79 -0
- package/src/agents/SubAgentManager.js +369 -0
- package/src/agents/Supervisor.js +192 -0
- package/src/channels/BaseChannel.js +104 -0
- package/src/channels/DiscordChannel.js +288 -0
- package/src/channels/EmailChannel.js +172 -0
- package/src/channels/GoogleChatChannel.js +316 -0
- package/src/channels/HttpChannel.js +26 -0
- package/src/channels/LineChannel.js +168 -0
- package/src/channels/SignalChannel.js +186 -0
- package/src/channels/SlackChannel.js +329 -0
- package/src/channels/TeamsChannel.js +272 -0
- package/src/channels/TelegramChannel.js +347 -0
- package/src/channels/WhatsAppChannel.js +219 -0
- package/src/channels/index.js +198 -0
- package/src/cli.js +1267 -0
- package/src/config/agentProfiles.js +120 -0
- package/src/config/channels.js +32 -0
- package/src/config/default.js +206 -0
- package/src/config/models.js +123 -0
- package/src/config/permissions.js +167 -0
- package/src/core/AgentLoop.js +446 -0
- package/src/core/Compaction.js +143 -0
- package/src/core/CostTracker.js +116 -0
- package/src/core/EventBus.js +46 -0
- package/src/core/Task.js +67 -0
- package/src/core/TaskQueue.js +206 -0
- package/src/core/TaskRunner.js +226 -0
- package/src/daemon/DaemonManager.js +301 -0
- package/src/hooks/HookRunner.js +230 -0
- package/src/index.js +482 -0
- package/src/mcp/MCPAgentRunner.js +112 -0
- package/src/mcp/MCPClient.js +186 -0
- package/src/mcp/MCPManager.js +412 -0
- package/src/models/ModelRouter.js +180 -0
- package/src/safety/AuditLog.js +135 -0
- package/src/safety/CircuitBreaker.js +126 -0
- package/src/safety/FilesystemGuard.js +169 -0
- package/src/safety/GitRollback.js +139 -0
- package/src/safety/HumanApproval.js +156 -0
- package/src/safety/InputSanitizer.js +72 -0
- package/src/safety/PermissionGuard.js +83 -0
- package/src/safety/Sandbox.js +70 -0
- package/src/safety/SecretScanner.js +100 -0
- package/src/safety/SecretVault.js +250 -0
- package/src/scheduler/Heartbeat.js +115 -0
- package/src/scheduler/Scheduler.js +228 -0
- package/src/services/models/outputSchema.js +15 -0
- package/src/services/openai.js +25 -0
- package/src/services/sessions.js +65 -0
- package/src/setup/theme.js +110 -0
- package/src/setup/wizard.js +788 -0
- package/src/skills/SkillLoader.js +168 -0
- package/src/storage/TaskStore.js +69 -0
- package/src/systemPrompt.js +526 -0
- package/src/tenants/TenantContext.js +19 -0
- package/src/tenants/TenantManager.js +379 -0
- package/src/tools/ToolRegistry.js +141 -0
- package/src/tools/applyPatch.js +144 -0
- package/src/tools/browserAutomation.js +223 -0
- package/src/tools/createDocument.js +265 -0
- package/src/tools/cronTool.js +105 -0
- package/src/tools/editFile.js +139 -0
- package/src/tools/executeCommand.js +123 -0
- package/src/tools/glob.js +67 -0
- package/src/tools/grep.js +121 -0
- package/src/tools/imageAnalysis.js +120 -0
- package/src/tools/index.js +173 -0
- package/src/tools/listDirectory.js +47 -0
- package/src/tools/manageAgents.js +47 -0
- package/src/tools/manageMCP.js +159 -0
- package/src/tools/memory.js +478 -0
- package/src/tools/messageChannel.js +45 -0
- package/src/tools/projectTracker.js +259 -0
- package/src/tools/readFile.js +52 -0
- package/src/tools/screenCapture.js +112 -0
- package/src/tools/searchContent.js +76 -0
- package/src/tools/searchFiles.js +75 -0
- package/src/tools/sendEmail.js +118 -0
- package/src/tools/sendFile.js +63 -0
- package/src/tools/textToSpeech.js +161 -0
- package/src/tools/transcribeAudio.js +82 -0
- package/src/tools/useMCP.js +29 -0
- package/src/tools/webFetch.js +150 -0
- package/src/tools/webSearch.js +134 -0
- package/src/tools/writeFile.js +26 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: obsidian
|
|
3
|
+
description: Read, create, edit, search, and manage notes in an Obsidian vault. Use when the user asks to create a note, find a note, update an Obsidian file, link notes, search their vault, or manage their knowledge base. Obsidian vaults are plain Markdown files — no special tools needed for basic operations.
|
|
4
|
+
triggers: obsidian, note, vault, knowledge base, zettelkasten, markdown note, create note, find note, link note, obsidian search, obsidian plugin
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## When to Use
|
|
8
|
+
|
|
9
|
+
✅ Create notes, read notes, search vault, edit frontmatter, create links between notes, list all notes, tag management, daily notes
|
|
10
|
+
|
|
11
|
+
❌ Obsidian UI automation (open a specific note visually) — use `obsidian://` URI scheme for that
|
|
12
|
+
|
|
13
|
+
## Find the Vault Location
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Obsidian tracks vaults here on macOS:
|
|
17
|
+
python3 -c "
|
|
18
|
+
import json, pathlib
|
|
19
|
+
config = pathlib.Path.home() / 'Library/Application Support/obsidian/obsidian.json'
|
|
20
|
+
if config.exists():
|
|
21
|
+
data = json.loads(config.read_text())
|
|
22
|
+
for vault in data.get('vaults', {}).values():
|
|
23
|
+
status = '✅ (open)' if vault.get('open') else ''
|
|
24
|
+
print(f\"{vault['path']} {status}\")
|
|
25
|
+
else:
|
|
26
|
+
print('Obsidian config not found — vault location unknown')
|
|
27
|
+
"
|
|
28
|
+
# Typical locations: ~/Documents/Obsidian/, ~/Notes/, ~/Documents/Notes/
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Read Notes
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# Find all notes in vault
|
|
35
|
+
find ~/Documents/Obsidian -name "*.md" | head -20
|
|
36
|
+
|
|
37
|
+
# Search by content
|
|
38
|
+
grep -r "search term" ~/Documents/Obsidian --include="*.md" -l
|
|
39
|
+
|
|
40
|
+
# Search with context (2 lines around match)
|
|
41
|
+
grep -r "search term" ~/Documents/Obsidian --include="*.md" -C 2
|
|
42
|
+
|
|
43
|
+
# Find notes with a specific tag
|
|
44
|
+
grep -r "^tags:.*tagname\|#tagname" ~/Documents/Obsidian --include="*.md" -l
|
|
45
|
+
|
|
46
|
+
# Find notes created today (macOS)
|
|
47
|
+
find ~/Documents/Obsidian -name "*.md" -newer $(date -v-1d +%Y-%m-%d 2>/dev/null || date -d "1 day ago" +%Y-%m-%d) 2>/dev/null
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Create a Note
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
#!/usr/bin/env python3
|
|
54
|
+
from pathlib import Path
|
|
55
|
+
from datetime import datetime
|
|
56
|
+
|
|
57
|
+
VAULT = Path.home() / "Documents/Obsidian" # adjust to actual vault path
|
|
58
|
+
|
|
59
|
+
def create_note(title: str, content: str, folder: str = "", tags: list = None):
|
|
60
|
+
"""Create a note with YAML frontmatter."""
|
|
61
|
+
tags = tags or []
|
|
62
|
+
date = datetime.now().strftime("%Y-%m-%d")
|
|
63
|
+
time = datetime.now().strftime("%H:%M")
|
|
64
|
+
|
|
65
|
+
frontmatter = f"""---
|
|
66
|
+
title: {title}
|
|
67
|
+
date: {date}
|
|
68
|
+
time: {time}
|
|
69
|
+
tags: [{', '.join(tags)}]
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
"""
|
|
73
|
+
target_dir = VAULT / folder if folder else VAULT
|
|
74
|
+
target_dir.mkdir(parents=True, exist_ok=True)
|
|
75
|
+
|
|
76
|
+
# Sanitize filename
|
|
77
|
+
filename = title.replace("/", "-").replace(":", "-") + ".md"
|
|
78
|
+
filepath = target_dir / filename
|
|
79
|
+
|
|
80
|
+
# Don't overwrite — append timestamp if exists
|
|
81
|
+
if filepath.exists():
|
|
82
|
+
ts = datetime.now().strftime("%Y%m%d%H%M%S")
|
|
83
|
+
filepath = target_dir / f"{title} ({ts}).md"
|
|
84
|
+
|
|
85
|
+
filepath.write_text(frontmatter + content, encoding="utf-8")
|
|
86
|
+
print(f"Created: {filepath}")
|
|
87
|
+
return filepath
|
|
88
|
+
|
|
89
|
+
# Example usage:
|
|
90
|
+
create_note(
|
|
91
|
+
title="Meeting Notes — Q1 Review",
|
|
92
|
+
content="## Attendees\n- Alice\n- Bob\n\n## Key Points\n- ...",
|
|
93
|
+
folder="Meetings",
|
|
94
|
+
tags=["meeting", "q1-2026"]
|
|
95
|
+
)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Create Daily Note
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
#!/usr/bin/env python3
|
|
102
|
+
from pathlib import Path
|
|
103
|
+
from datetime import datetime
|
|
104
|
+
|
|
105
|
+
VAULT = Path.home() / "Documents/Obsidian"
|
|
106
|
+
DAILY_FOLDER = VAULT / "Daily Notes"
|
|
107
|
+
DAILY_FOLDER.mkdir(parents=True, exist_ok=True)
|
|
108
|
+
|
|
109
|
+
today = datetime.now()
|
|
110
|
+
filename = today.strftime("%Y-%m-%d") + ".md"
|
|
111
|
+
filepath = DAILY_FOLDER / filename
|
|
112
|
+
|
|
113
|
+
if not filepath.exists():
|
|
114
|
+
content = f"""---
|
|
115
|
+
date: {today.strftime('%Y-%m-%d')}
|
|
116
|
+
tags: [daily]
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
# {today.strftime('%A, %B %d, %Y')}
|
|
120
|
+
|
|
121
|
+
## 🎯 Focus Today
|
|
122
|
+
|
|
123
|
+
## 📝 Notes
|
|
124
|
+
|
|
125
|
+
## ✅ Tasks
|
|
126
|
+
- [ ]
|
|
127
|
+
|
|
128
|
+
## 💭 Reflections
|
|
129
|
+
|
|
130
|
+
"""
|
|
131
|
+
filepath.write_text(content)
|
|
132
|
+
print(f"Created daily note: {filepath}")
|
|
133
|
+
else:
|
|
134
|
+
print(f"Daily note already exists: {filepath}")
|
|
135
|
+
|
|
136
|
+
# Open it in Obsidian
|
|
137
|
+
import subprocess
|
|
138
|
+
subprocess.run(["open", f"obsidian://open?vault=Obsidian&file=Daily Notes/{today.strftime('%Y-%m-%d')}"])
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Edit Existing Note
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
#!/usr/bin/env python3
|
|
145
|
+
from pathlib import Path
|
|
146
|
+
|
|
147
|
+
def append_to_note(note_path: str, content: str, section: str = None):
|
|
148
|
+
"""Append content to a note, optionally under a specific section."""
|
|
149
|
+
path = Path(note_path)
|
|
150
|
+
existing = path.read_text(encoding="utf-8")
|
|
151
|
+
|
|
152
|
+
if section:
|
|
153
|
+
# Insert under the section heading
|
|
154
|
+
marker = f"## {section}"
|
|
155
|
+
if marker in existing:
|
|
156
|
+
idx = existing.index(marker) + len(marker)
|
|
157
|
+
# Find next line after heading
|
|
158
|
+
next_line = existing.find("\n", idx) + 1
|
|
159
|
+
updated = existing[:next_line] + "\n" + content + "\n" + existing[next_line:]
|
|
160
|
+
else:
|
|
161
|
+
updated = existing + f"\n## {section}\n\n{content}\n"
|
|
162
|
+
else:
|
|
163
|
+
updated = existing.rstrip() + f"\n\n{content}\n"
|
|
164
|
+
|
|
165
|
+
path.write_text(updated, encoding="utf-8")
|
|
166
|
+
print(f"Updated: {path}")
|
|
167
|
+
|
|
168
|
+
# Example: append a task under ## ✅ Tasks
|
|
169
|
+
append_to_note(
|
|
170
|
+
"/path/to/note.md",
|
|
171
|
+
"- [ ] New task item",
|
|
172
|
+
section="✅ Tasks"
|
|
173
|
+
)
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Create Linked Notes (Wiki Links)
|
|
177
|
+
|
|
178
|
+
Obsidian uses `[[Note Title]]` syntax for links. When creating a note that references others:
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
# Create a note with backlinks
|
|
182
|
+
content = """
|
|
183
|
+
## Related Research
|
|
184
|
+
|
|
185
|
+
See [[Market Analysis 2026]] for context.
|
|
186
|
+
Building on [[Previous Meeting Notes]].
|
|
187
|
+
|
|
188
|
+
## Summary
|
|
189
|
+
...
|
|
190
|
+
"""
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Open Note in Obsidian (macOS)
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
# Open specific note
|
|
197
|
+
open "obsidian://open?vault=VAULT_NAME&file=PATH/TO/NOTE"
|
|
198
|
+
|
|
199
|
+
# Open search in Obsidian
|
|
200
|
+
open "obsidian://search?vault=VAULT_NAME&query=search+term"
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Error Handling
|
|
204
|
+
|
|
205
|
+
- **Vault not found**: read `~/Library/Application Support/obsidian/obsidian.json` to locate vaults; ask user to confirm vault path if multiple exist
|
|
206
|
+
- **File already exists**: always check before writing; either append content or create new file with timestamp suffix
|
|
207
|
+
- **Encoding issues**: always use `encoding="utf-8"` when reading/writing `.md` files
|
package/skills/pdf.md
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pdf
|
|
3
|
+
description: Create, read, extract text from, merge, split, rotate, compress, and manipulate PDF files. Use when the user asks to create a PDF report/document, extract text from a PDF, merge PDFs, convert to PDF, or do any PDF operation. Works on macOS and Linux without paid tools.
|
|
4
|
+
triggers: pdf, create pdf, read pdf, extract pdf, merge pdf, split pdf, convert to pdf, pdf report, pdf document, compress pdf, rotate pdf
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## When to Use
|
|
8
|
+
|
|
9
|
+
✅ Create PDF reports/documents, extract text from PDFs, merge multiple PDFs, split pages, rotate, compress, convert HTML/Markdown to PDF
|
|
10
|
+
|
|
11
|
+
❌ PDF form filling with complex logic → use PyPDF2 + pdfrw; PDF digital signatures → use specialized tools
|
|
12
|
+
|
|
13
|
+
## Method 1: Create PDF from HTML (best quality, macOS)
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Install wkhtmltopdf (one-time)
|
|
17
|
+
brew install wkhtmltopdf
|
|
18
|
+
|
|
19
|
+
# Create HTML file, then convert
|
|
20
|
+
cat > /tmp/report.html << 'EOF'
|
|
21
|
+
<!DOCTYPE html>
|
|
22
|
+
<html>
|
|
23
|
+
<head>
|
|
24
|
+
<meta charset="UTF-8">
|
|
25
|
+
<style>
|
|
26
|
+
body { font-family: -apple-system, Arial, sans-serif; margin: 40px; color: #222; }
|
|
27
|
+
h1 { color: #1a1a2e; border-bottom: 2px solid #eee; padding-bottom: 10px; }
|
|
28
|
+
h2 { color: #444; margin-top: 30px; }
|
|
29
|
+
table { border-collapse: collapse; width: 100%; margin: 16px 0; }
|
|
30
|
+
th { background: #f0f4ff; padding: 10px; text-align: left; border: 1px solid #ddd; }
|
|
31
|
+
td { padding: 8px 10px; border: 1px solid #eee; }
|
|
32
|
+
.highlight { background: #fffbea; padding: 12px; border-left: 4px solid #f0c040; }
|
|
33
|
+
</style>
|
|
34
|
+
</head>
|
|
35
|
+
<body>
|
|
36
|
+
<h1>Report Title</h1>
|
|
37
|
+
<p>Content here</p>
|
|
38
|
+
</body>
|
|
39
|
+
</html>
|
|
40
|
+
EOF
|
|
41
|
+
|
|
42
|
+
wkhtmltopdf \
|
|
43
|
+
--page-size A4 \
|
|
44
|
+
--margin-top 20mm --margin-bottom 20mm \
|
|
45
|
+
--margin-left 15mm --margin-right 15mm \
|
|
46
|
+
--encoding UTF-8 \
|
|
47
|
+
--footer-center "[page] / [topage]" \
|
|
48
|
+
--footer-font-size 9 \
|
|
49
|
+
/tmp/report.html /tmp/report.pdf
|
|
50
|
+
|
|
51
|
+
echo "Created: /tmp/report.pdf"
|
|
52
|
+
open /tmp/report.pdf # preview on macOS
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Method 2: Create PDF with Python (cross-platform, no external binaries)
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
#!/usr/bin/env python3
|
|
59
|
+
"""Create a PDF without any system binaries — uses reportlab."""
|
|
60
|
+
# Install once: pip install reportlab
|
|
61
|
+
from reportlab.lib.pagesizes import A4
|
|
62
|
+
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
|
63
|
+
from reportlab.lib.units import mm
|
|
64
|
+
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle
|
|
65
|
+
from reportlab.lib import colors
|
|
66
|
+
|
|
67
|
+
output_path = "/tmp/report.pdf"
|
|
68
|
+
doc = SimpleDocTemplate(output_path, pagesize=A4,
|
|
69
|
+
leftMargin=20*mm, rightMargin=20*mm,
|
|
70
|
+
topMargin=20*mm, bottomMargin=20*mm)
|
|
71
|
+
|
|
72
|
+
styles = getSampleStyleSheet()
|
|
73
|
+
story = []
|
|
74
|
+
|
|
75
|
+
# Title
|
|
76
|
+
title_style = ParagraphStyle('Title', parent=styles['Title'],
|
|
77
|
+
fontSize=24, spaceAfter=12)
|
|
78
|
+
story.append(Paragraph("Report Title", title_style))
|
|
79
|
+
story.append(Spacer(1, 8*mm))
|
|
80
|
+
|
|
81
|
+
# Body text
|
|
82
|
+
story.append(Paragraph("Your content here. Supports <b>bold</b>, <i>italic</i>, and links.", styles['BodyText']))
|
|
83
|
+
story.append(Spacer(1, 6*mm))
|
|
84
|
+
|
|
85
|
+
# Table
|
|
86
|
+
data = [['Column A', 'Column B', 'Column C'],
|
|
87
|
+
['Row 1', 'Value', '$100'],
|
|
88
|
+
['Row 2', 'Value', '$200']]
|
|
89
|
+
table = Table(data, colWidths=[60*mm, 60*mm, 40*mm])
|
|
90
|
+
table.setStyle(TableStyle([
|
|
91
|
+
('BACKGROUND', (0,0), (-1,0), colors.HexColor('#e8eaf6')),
|
|
92
|
+
('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
|
|
93
|
+
('GRID', (0,0), (-1,-1), 0.5, colors.HexColor('#cccccc')),
|
|
94
|
+
('ROWBACKGROUNDS', (0,1), (-1,-1), [colors.white, colors.HexColor('#f8f9ff')]),
|
|
95
|
+
('PADDING', (0,0), (-1,-1), 8),
|
|
96
|
+
]))
|
|
97
|
+
story.append(table)
|
|
98
|
+
|
|
99
|
+
doc.build(story)
|
|
100
|
+
print(f"Created: {output_path}")
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Read / Extract Text from PDF
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
#!/usr/bin/env python3
|
|
107
|
+
# pip install pdfplumber (better than pypdf2 for text extraction)
|
|
108
|
+
import pdfplumber, sys
|
|
109
|
+
|
|
110
|
+
pdf_path = sys.argv[1] if len(sys.argv) > 1 else "/path/to/file.pdf"
|
|
111
|
+
|
|
112
|
+
with pdfplumber.open(pdf_path) as pdf:
|
|
113
|
+
print(f"Pages: {len(pdf.pages)}")
|
|
114
|
+
for i, page in enumerate(pdf.pages, 1):
|
|
115
|
+
text = page.extract_text()
|
|
116
|
+
if text:
|
|
117
|
+
print(f"\n--- Page {i} ---\n{text}")
|
|
118
|
+
|
|
119
|
+
# Also extract tables
|
|
120
|
+
tables = page.extract_tables()
|
|
121
|
+
for t in tables:
|
|
122
|
+
print(f"\n[Table on page {i}]")
|
|
123
|
+
for row in t:
|
|
124
|
+
print(" | ".join(str(c or '') for c in row))
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Merge PDFs
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
#!/usr/bin/env python3
|
|
131
|
+
# pip install pypdf
|
|
132
|
+
from pypdf import PdfWriter, PdfReader
|
|
133
|
+
import sys
|
|
134
|
+
|
|
135
|
+
output = "/tmp/merged.pdf"
|
|
136
|
+
writer = PdfWriter()
|
|
137
|
+
|
|
138
|
+
for path in sys.argv[1:]:
|
|
139
|
+
reader = PdfReader(path)
|
|
140
|
+
for page in reader.pages:
|
|
141
|
+
writer.add_page(page)
|
|
142
|
+
|
|
143
|
+
with open(output, "wb") as f:
|
|
144
|
+
writer.write(f)
|
|
145
|
+
print(f"Merged {len(sys.argv)-1} PDFs → {output}")
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Split PDF (extract page range)
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
#!/usr/bin/env python3
|
|
152
|
+
# pip install pypdf
|
|
153
|
+
from pypdf import PdfWriter, PdfReader
|
|
154
|
+
|
|
155
|
+
input_pdf = "/path/to/input.pdf"
|
|
156
|
+
output_pdf = "/tmp/extracted_pages.pdf"
|
|
157
|
+
start_page = 0 # 0-indexed
|
|
158
|
+
end_page = 5 # exclusive
|
|
159
|
+
|
|
160
|
+
reader = PdfReader(input_pdf)
|
|
161
|
+
writer = PdfWriter()
|
|
162
|
+
for i in range(start_page, min(end_page, len(reader.pages))):
|
|
163
|
+
writer.add_page(reader.pages[i])
|
|
164
|
+
with open(output_pdf, "wb") as f:
|
|
165
|
+
writer.write(f)
|
|
166
|
+
print(f"Extracted pages {start_page+1}-{end_page} → {output_pdf}")
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Compress PDF (macOS, no install)
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
# Uses macOS built-in Quartz filter
|
|
173
|
+
"/System/Library/Printers/Libraries/quartzfilter" \
|
|
174
|
+
/System/Library/Filters/Reduce\ File\ Size.qfilter \
|
|
175
|
+
/path/to/input.pdf /tmp/compressed.pdf
|
|
176
|
+
|
|
177
|
+
# Or via Python with ghostscript
|
|
178
|
+
gs -sDEVICE=pdfwrite -dCompatibilityLevel=1.5 \
|
|
179
|
+
-dPDFSETTINGS=/ebook \
|
|
180
|
+
-dNOPAUSE -dQUIET -dBATCH \
|
|
181
|
+
-sOutputFile=/tmp/compressed.pdf /path/to/input.pdf
|
|
182
|
+
# PDFSET options: /screen (72dpi) /ebook (150dpi) /printer (300dpi) /prepress (300dpi HQ)
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Convert Markdown → PDF
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
# pip install md-to-pdf OR brew install pandoc
|
|
189
|
+
# With pandoc (best quality):
|
|
190
|
+
pandoc input.md -o output.pdf \
|
|
191
|
+
--pdf-engine=wkhtmltopdf \
|
|
192
|
+
-V geometry:margin=25mm \
|
|
193
|
+
-V fontsize=11pt
|
|
194
|
+
|
|
195
|
+
# Quick and dirty with Python:
|
|
196
|
+
pip install markdown weasyprint
|
|
197
|
+
python3 -c "
|
|
198
|
+
import markdown, weasyprint, pathlib
|
|
199
|
+
md = pathlib.Path('input.md').read_text()
|
|
200
|
+
html = markdown.markdown(md, extensions=['tables','fenced_code'])
|
|
201
|
+
weasyprint.HTML(string=f'<html><body style=\"font-family:sans-serif;margin:40px\">{html}</body></html>').write_pdf('output.pdf')
|
|
202
|
+
print('output.pdf')
|
|
203
|
+
"
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## After Creating PDF
|
|
207
|
+
|
|
208
|
+
1. Report the output path: "PDF saved to `/tmp/report.pdf`"
|
|
209
|
+
2. On macOS, offer to open it: `executeCommand("open /tmp/report.pdf")`
|
|
210
|
+
3. If the user wants to send it, use `sendFile("/tmp/report.pdf", channel, sessionId)`
|
|
211
|
+
4. For large reports: mention page count and file size
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: research
|
|
3
|
+
description: Use when researching topics, gathering information, or answering questions
|
|
4
|
+
triggers: research, search, find, look up, what is, how does, explain, learn, investigate, compare
|
|
5
|
+
---
|
|
6
|
+
You are an expert researcher. When doing research tasks:
|
|
7
|
+
|
|
8
|
+
1. **Search first** — Use webSearch to find relevant information before answering.
|
|
9
|
+
2. **Multiple sources** — Check at least 2-3 sources to verify information.
|
|
10
|
+
3. **Cite sources** — Include URLs and source names in your response.
|
|
11
|
+
4. **Be current** — Prefer recent sources over older ones.
|
|
12
|
+
5. **Summarize clearly** — Present findings in a structured, easy-to-read format.
|
|
13
|
+
6. **Distinguish fact from opinion** — Clearly mark what is established fact vs. interpretation.
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: skill-creator
|
|
3
|
+
description: Create new Daemora skills. Use when the user asks to create a new skill, package a capability, teach the agent a new behavior, or add a new skill file. A skill is a Markdown file in the skills/ directory that gets auto-loaded when matching triggers are detected.
|
|
4
|
+
triggers: create skill, new skill, add skill, make skill, package skill, teach agent, skill creator, write skill
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## What Is a Skill
|
|
8
|
+
|
|
9
|
+
A skill is a `.md` file in the `skills/` directory. When a user's task matches the skill's `triggers`, the skill's instructions are injected into the agent's system prompt for that task. Skills give the agent specialized knowledge, commands, and workflows for specific domains.
|
|
10
|
+
|
|
11
|
+
## Skill File Format
|
|
12
|
+
|
|
13
|
+
```markdown
|
|
14
|
+
---
|
|
15
|
+
name: skill-name # unique slug, lowercase-hyphen
|
|
16
|
+
description: One sentence describing WHAT this skill does and WHEN to use it.
|
|
17
|
+
Include the specific trigger phrases. Be comprehensive — the description
|
|
18
|
+
is how the agent decides whether to load this skill.
|
|
19
|
+
triggers: keyword1, keyword2, phrase, another phrase
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## When to Use
|
|
23
|
+
|
|
24
|
+
✅ Specific cases where this skill applies
|
|
25
|
+
❌ Cases where it does NOT apply (prevents false triggers)
|
|
26
|
+
|
|
27
|
+
## Core Instructions
|
|
28
|
+
|
|
29
|
+
[Domain-specific knowledge, commands, workflows]
|
|
30
|
+
|
|
31
|
+
## Examples / Commands
|
|
32
|
+
|
|
33
|
+
[Concrete, working examples with real commands the agent can copy-paste]
|
|
34
|
+
|
|
35
|
+
## Error Handling
|
|
36
|
+
|
|
37
|
+
[What to do when things go wrong]
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Creation Process
|
|
41
|
+
|
|
42
|
+
### Step 1 — Gather requirements
|
|
43
|
+
|
|
44
|
+
Ask (or infer from context):
|
|
45
|
+
- What specific task should this skill handle?
|
|
46
|
+
- What tools/commands/APIs does it use?
|
|
47
|
+
- Does it require any external tools to be installed?
|
|
48
|
+
- What platform does it run on (macOS/Linux/all)?
|
|
49
|
+
- What are the "don't use this for" cases?
|
|
50
|
+
|
|
51
|
+
### Step 2 — Design the skill
|
|
52
|
+
|
|
53
|
+
Plan what to include:
|
|
54
|
+
- **Trigger keywords**: think of every way a user might ask for this — colloquial terms, technical terms, action verbs
|
|
55
|
+
- **Core commands**: real, tested commands with correct flags
|
|
56
|
+
- **Error cases**: the 3-5 most common failure modes and their fixes
|
|
57
|
+
- **Response format**: how should the agent present results to the user
|
|
58
|
+
|
|
59
|
+
### Step 3 — Write and save
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
#!/usr/bin/env python3
|
|
63
|
+
"""Create a new Daemora skill file."""
|
|
64
|
+
import re
|
|
65
|
+
from pathlib import Path
|
|
66
|
+
|
|
67
|
+
SKILLS_DIR = Path(__file__).parent.parent / "skills"
|
|
68
|
+
|
|
69
|
+
def create_skill(name: str, description: str, triggers: list[str], content: str) -> Path:
|
|
70
|
+
"""
|
|
71
|
+
name: slug like "my-skill"
|
|
72
|
+
description: full description for triggering
|
|
73
|
+
triggers: list of trigger keywords/phrases
|
|
74
|
+
content: markdown body of the skill (without frontmatter)
|
|
75
|
+
"""
|
|
76
|
+
# Validate name
|
|
77
|
+
if not re.match(r'^[a-z0-9-]+$', name):
|
|
78
|
+
raise ValueError(f"Skill name must be lowercase-hyphen: {name}")
|
|
79
|
+
|
|
80
|
+
trigger_str = ", ".join(triggers)
|
|
81
|
+
frontmatter = f"""---
|
|
82
|
+
name: {name}
|
|
83
|
+
description: {description}
|
|
84
|
+
triggers: {trigger_str}
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
"""
|
|
88
|
+
skill_path = SKILLS_DIR / f"{name}.md"
|
|
89
|
+
if skill_path.exists():
|
|
90
|
+
print(f"⚠️ Skill already exists: {skill_path}")
|
|
91
|
+
overwrite = input("Overwrite? (y/N): ").lower() == "y"
|
|
92
|
+
if not overwrite:
|
|
93
|
+
return skill_path
|
|
94
|
+
|
|
95
|
+
skill_path.write_text(frontmatter + content, encoding="utf-8")
|
|
96
|
+
print(f"✅ Skill created: {skill_path}")
|
|
97
|
+
return skill_path
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Step 4 — Reload skills
|
|
101
|
+
|
|
102
|
+
After creating a skill:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
curl -s -X POST http://localhost:8081/skills/reload
|
|
106
|
+
# → {"loaded": 12, "skills": ["coding", "research", ..., "new-skill"]}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Or restart the agent: `daemora start`
|
|
110
|
+
|
|
111
|
+
## Skill Quality Checklist
|
|
112
|
+
|
|
113
|
+
Before finalizing a skill, verify:
|
|
114
|
+
|
|
115
|
+
- [ ] `description` clearly states WHAT the skill does and the key trigger phrases
|
|
116
|
+
- [ ] `triggers` covers all reasonable ways a user would ask for this
|
|
117
|
+
- [ ] Has "When to Use / When NOT to Use" section
|
|
118
|
+
- [ ] All commands are real and tested — no fictional examples
|
|
119
|
+
- [ ] Platform requirements noted (macOS-only, requires X installed, etc.)
|
|
120
|
+
- [ ] Error handling covers the most common failure modes
|
|
121
|
+
- [ ] No duplicate of existing skills — check `ls skills/`
|
|
122
|
+
|
|
123
|
+
## Example: Creating a "Trello" Skill
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
name: trello
|
|
127
|
+
description: Manage Trello boards, lists, and cards via the Trello REST API...
|
|
128
|
+
triggers: trello, kanban board, create card, move card, trello list...
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Content should include:
|
|
132
|
+
1. API auth setup (`TRELLO_API_KEY` + `TRELLO_TOKEN`)
|
|
133
|
+
2. Get boards/lists/cards commands
|
|
134
|
+
3. Create/move/archive card commands
|
|
135
|
+
4. Webhook setup for automation
|
|
136
|
+
|
|
137
|
+
## Do NOT Create Skills For
|
|
138
|
+
|
|
139
|
+
- One-time tasks (just do the task directly)
|
|
140
|
+
- Things the agent already handles with its 36 built-in tools
|
|
141
|
+
- Duplicates of existing skills (check `skills/` first)
|
|
142
|
+
- Platform-specific things on wrong platform (e.g., Apple Notes skill on Linux)
|