daemora 1.0.1 → 1.0.3
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 +106 -76
- package/SOUL.md +100 -28
- package/config/mcp.json +9 -9
- package/package.json +15 -8
- package/skills/apple-notes.md +0 -52
- package/skills/apple-reminders.md +1 -87
- package/skills/camsnap.md +20 -144
- package/skills/coding.md +7 -7
- package/skills/documents.md +6 -6
- package/skills/email.md +6 -6
- package/skills/gif-search.md +28 -171
- package/skills/healthcheck.md +21 -203
- package/skills/image-gen.md +24 -123
- package/skills/model-usage.md +18 -165
- package/skills/obsidian.md +28 -174
- package/skills/pdf.md +30 -181
- package/skills/research.md +6 -6
- package/skills/skill-creator.md +35 -111
- package/skills/spotify.md +2 -17
- package/skills/summarize.md +36 -193
- package/skills/things.md +23 -175
- package/skills/tmux.md +1 -91
- package/skills/trello.md +32 -157
- package/skills/video-frames.md +26 -166
- package/skills/weather.md +6 -6
- package/src/a2a/A2AClient.js +2 -2
- package/src/a2a/A2AServer.js +6 -6
- package/src/a2a/AgentCard.js +2 -2
- package/src/agents/SubAgentManager.js +61 -19
- package/src/agents/Supervisor.js +4 -4
- package/src/channels/BaseChannel.js +6 -6
- package/src/channels/BlueBubblesChannel.js +112 -0
- package/src/channels/DiscordChannel.js +8 -8
- package/src/channels/EmailChannel.js +54 -26
- package/src/channels/FeishuChannel.js +140 -0
- package/src/channels/GoogleChatChannel.js +8 -8
- package/src/channels/HttpChannel.js +2 -2
- package/src/channels/IRCChannel.js +144 -0
- package/src/channels/LineChannel.js +13 -13
- package/src/channels/MatrixChannel.js +97 -0
- package/src/channels/MattermostChannel.js +119 -0
- package/src/channels/NextcloudChannel.js +133 -0
- package/src/channels/NostrChannel.js +175 -0
- package/src/channels/SignalChannel.js +9 -9
- package/src/channels/SlackChannel.js +10 -10
- package/src/channels/TeamsChannel.js +10 -10
- package/src/channels/TelegramChannel.js +8 -8
- package/src/channels/TwitchChannel.js +128 -0
- package/src/channels/WhatsAppChannel.js +10 -10
- package/src/channels/ZaloChannel.js +119 -0
- package/src/channels/iMessageChannel.js +150 -0
- package/src/channels/index.js +241 -11
- package/src/cli.js +835 -38
- package/src/config/agentProfiles.js +19 -19
- package/src/config/channels.js +1 -1
- package/src/config/default.js +12 -7
- package/src/config/models.js +3 -3
- package/src/config/permissions.js +2 -2
- package/src/core/AgentLoop.js +13 -13
- package/src/core/Compaction.js +3 -3
- package/src/core/CostTracker.js +2 -2
- package/src/core/EventBus.js +15 -15
- package/src/core/TaskQueue.js +24 -7
- package/src/core/TaskRunner.js +19 -6
- package/src/daemon/DaemonManager.js +4 -4
- package/src/hooks/HookRunner.js +4 -4
- package/src/index.js +6 -2
- package/src/mcp/MCPAgentRunner.js +3 -3
- package/src/mcp/MCPClient.js +9 -9
- package/src/mcp/MCPManager.js +14 -14
- package/src/models/ModelRouter.js +2 -2
- package/src/safety/AuditLog.js +3 -3
- package/src/safety/CircuitBreaker.js +2 -2
- package/src/safety/CommandGuard.js +132 -0
- package/src/safety/FilesystemGuard.js +23 -3
- package/src/safety/GitRollback.js +5 -5
- package/src/safety/HumanApproval.js +9 -9
- package/src/safety/InputSanitizer.js +81 -8
- package/src/safety/PermissionGuard.js +2 -2
- package/src/safety/Sandbox.js +1 -1
- package/src/safety/SecretScanner.js +90 -28
- package/src/safety/SecretVault.js +2 -2
- package/src/scheduler/Heartbeat.js +3 -3
- package/src/scheduler/Scheduler.js +6 -6
- package/src/setup/theme.js +171 -66
- package/src/setup/wizard.js +432 -57
- package/src/skills/SkillLoader.js +145 -8
- package/src/storage/TaskStore.js +39 -15
- package/src/systemPrompt.js +45 -43
- package/src/tenants/TenantManager.js +79 -22
- package/src/tools/ToolRegistry.js +3 -3
- package/src/tools/applyPatch.js +2 -2
- package/src/tools/browserAutomation.js +4 -4
- package/src/tools/calendar.js +155 -0
- package/src/tools/clipboard.js +71 -0
- package/src/tools/contacts.js +138 -0
- package/src/tools/createDocument.js +2 -2
- package/src/tools/cronTool.js +14 -14
- package/src/tools/database.js +165 -0
- package/src/tools/editFile.js +10 -10
- package/src/tools/executeCommand.js +11 -3
- package/src/tools/generateImage.js +79 -0
- package/src/tools/gitTool.js +141 -0
- package/src/tools/glob.js +1 -1
- package/src/tools/googlePlaces.js +136 -0
- package/src/tools/grep.js +2 -2
- package/src/tools/iMessageTool.js +86 -0
- package/src/tools/imageAnalysis.js +3 -3
- package/src/tools/index.js +56 -2
- package/src/tools/makeVoiceCall.js +283 -0
- package/src/tools/manageAgents.js +2 -2
- package/src/tools/manageMCP.js +38 -20
- package/src/tools/memory.js +25 -32
- package/src/tools/messageChannel.js +1 -1
- package/src/tools/notification.js +90 -0
- package/src/tools/philipsHue.js +147 -0
- package/src/tools/projectTracker.js +8 -8
- package/src/tools/readFile.js +1 -1
- package/src/tools/readPDF.js +73 -0
- package/src/tools/screenCapture.js +6 -6
- package/src/tools/searchContent.js +2 -2
- package/src/tools/searchFiles.js +1 -1
- package/src/tools/sendEmail.js +79 -24
- package/src/tools/sendFile.js +4 -4
- package/src/tools/sonos.js +137 -0
- package/src/tools/sshTool.js +130 -0
- package/src/tools/textToSpeech.js +5 -5
- package/src/tools/transcribeAudio.js +4 -4
- package/src/tools/useMCP.js +4 -4
- package/src/tools/webFetch.js +2 -2
- package/src/tools/webSearch.js +1 -1
- package/src/utils/Embeddings.js +79 -0
- package/src/voice/VoiceSessionManager.js +170 -0
- package/src/voice/VoiceWebhook.js +188 -0
package/skills/things.md
CHANGED
|
@@ -1,184 +1,20 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: things
|
|
3
|
-
description: Manage Things 3 tasks, projects, and areas on macOS. Create todos with due dates, deadlines, checklists, tags, and notes. List inbox, today,
|
|
3
|
+
description: Manage Things 3 tasks, projects, and areas on macOS. Create todos with due dates, deadlines, checklists, tags, and notes. List inbox, today, and upcoming tasks. Use when the user asks to add a task to Things, check their to-do list, create a project, or manage Things 3.
|
|
4
4
|
triggers: things, things 3, add task, todo, to-do, create task, things inbox, things today, upcoming tasks, things project, things area, things tag
|
|
5
|
+
metadata: {"daemora": {"emoji": "✅", "install": ["brew install --cask things"], "os": ["darwin"]}}
|
|
5
6
|
---
|
|
6
7
|
|
|
7
|
-
##
|
|
8
|
-
|
|
9
|
-
✅ Add todos with dates/tags/projects, read inbox/today/upcoming, search tasks, move to a project, mark complete, create checklists
|
|
10
|
-
|
|
11
|
-
❌ Complex project management across teams (use Trello) — Things is personal task management; no API, no web access
|
|
12
|
-
|
|
13
|
-
## Requirements
|
|
14
|
-
|
|
15
|
-
- macOS with Things 3 installed (`brew install --cask things`)
|
|
16
|
-
- For read operations: grant Full Disk Access to Terminal/Daemora in System Settings → Privacy & Security
|
|
17
|
-
- For write operations: Things URL scheme (no extra permissions needed)
|
|
18
|
-
|
|
19
|
-
## Read Tasks
|
|
8
|
+
## Add task (URL scheme - no permissions needed)
|
|
20
9
|
|
|
21
10
|
```bash
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
# List today's tasks
|
|
27
|
-
sqlite3 "$THINGS_DB" "
|
|
28
|
-
SELECT title, dueDate, notes
|
|
29
|
-
FROM TMTask
|
|
30
|
-
WHERE status = 0
|
|
31
|
-
AND (startDate <= strftime('%s','now') OR startDate IS NULL)
|
|
32
|
-
AND trashed = 0
|
|
33
|
-
ORDER BY dueDate ASC
|
|
34
|
-
LIMIT 20;"
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
## Python Helper (Read + Write)
|
|
38
|
-
|
|
39
|
-
```python
|
|
40
|
-
#!/usr/bin/env python3
|
|
41
|
-
import subprocess, glob, sqlite3, os, urllib.parse
|
|
42
|
-
from datetime import datetime, date
|
|
43
|
-
from pathlib import Path
|
|
44
|
-
|
|
45
|
-
# ── Read: direct SQLite access ─────────────────────────────────────────────
|
|
46
|
-
|
|
47
|
-
def find_db() -> str:
|
|
48
|
-
"""Locate Things 3 SQLite database."""
|
|
49
|
-
patterns = [
|
|
50
|
-
os.path.expanduser("~/Library/Group Containers/*/Things/Database5/main.sqlite"),
|
|
51
|
-
os.path.expanduser("~/Library/Group Containers/*/ThingsData-*/Things/Database5/main.sqlite"),
|
|
52
|
-
]
|
|
53
|
-
for p in patterns:
|
|
54
|
-
matches = glob.glob(p)
|
|
55
|
-
if matches:
|
|
56
|
-
return matches[0]
|
|
57
|
-
raise FileNotFoundError("Things 3 database not found. Make sure Things 3 is installed and has been opened at least once.")
|
|
58
|
-
|
|
59
|
-
def get_tasks(area: str = None, tag: str = None, limit: int = 30) -> list[dict]:
|
|
60
|
-
"""Get pending tasks from Things 3."""
|
|
61
|
-
db_path = find_db()
|
|
62
|
-
conn = sqlite3.connect(db_path)
|
|
63
|
-
conn.row_factory = sqlite3.Row
|
|
64
|
-
cur = conn.cursor()
|
|
65
|
-
|
|
66
|
-
query = """
|
|
67
|
-
SELECT t.uuid, t.title, t.notes, t.dueDate, t.deadline,
|
|
68
|
-
p.title as project, a.title as area
|
|
69
|
-
FROM TMTask t
|
|
70
|
-
LEFT JOIN TMTask p ON t.project = p.uuid
|
|
71
|
-
LEFT JOIN TMArea a ON t.area = a.uuid
|
|
72
|
-
WHERE t.status = 0 AND t.trashed = 0 AND t.type = 0
|
|
73
|
-
"""
|
|
74
|
-
params = []
|
|
75
|
-
if area:
|
|
76
|
-
query += " AND a.title LIKE ?"
|
|
77
|
-
params.append(f"%{area}%")
|
|
78
|
-
if tag:
|
|
79
|
-
query += """ AND t.uuid IN (
|
|
80
|
-
SELECT task FROM TMTaskTag tt
|
|
81
|
-
JOIN TMTag tg ON tt.tag = tg.uuid
|
|
82
|
-
WHERE tg.title LIKE ?)"""
|
|
83
|
-
params.append(f"%{tag}%")
|
|
84
|
-
query += f" ORDER BY t.dueDate ASC NULLS LAST LIMIT {limit}"
|
|
85
|
-
|
|
86
|
-
rows = cur.execute(query, params).fetchall()
|
|
87
|
-
conn.close()
|
|
88
|
-
return [dict(r) for r in rows]
|
|
89
|
-
|
|
90
|
-
def get_today() -> list[dict]:
|
|
91
|
-
"""Get tasks scheduled for today or overdue."""
|
|
92
|
-
db_path = find_db()
|
|
93
|
-
today_ts = int(datetime.combine(date.today(), datetime.min.time()).timestamp())
|
|
94
|
-
conn = sqlite3.connect(db_path)
|
|
95
|
-
conn.row_factory = sqlite3.Row
|
|
96
|
-
cur = conn.cursor()
|
|
97
|
-
rows = cur.execute("""
|
|
98
|
-
SELECT t.uuid, t.title, t.notes, t.dueDate, t.deadline
|
|
99
|
-
FROM TMTask t
|
|
100
|
-
WHERE t.status = 0 AND t.trashed = 0 AND t.type = 0
|
|
101
|
-
AND t.startDate IS NOT NULL AND t.startDate <= ?
|
|
102
|
-
ORDER BY t.dueDate ASC NULLS LAST
|
|
103
|
-
""", (today_ts,)).fetchall()
|
|
104
|
-
conn.close()
|
|
105
|
-
return [dict(r) for r in rows]
|
|
106
|
-
|
|
107
|
-
# ── Write: Things URL scheme ────────────────────────────────────────────────
|
|
108
|
-
|
|
109
|
-
def add_task(
|
|
110
|
-
title: str,
|
|
111
|
-
notes: str = "",
|
|
112
|
-
when: str = "", # "today", "tomorrow", "evening", "anytime", "someday", or "YYYY-MM-DD"
|
|
113
|
-
deadline: str = "", # "YYYY-MM-DD"
|
|
114
|
-
tags: list[str] = None,
|
|
115
|
-
list_name: str = "", # project or area name
|
|
116
|
-
checklist: list[str] = None,
|
|
117
|
-
heading: str = "",
|
|
118
|
-
) -> str:
|
|
119
|
-
"""Add a task to Things 3 using the URL scheme."""
|
|
120
|
-
params = {"title": title}
|
|
121
|
-
if notes: params["notes"] = notes
|
|
122
|
-
if when: params["when"] = when
|
|
123
|
-
if deadline: params["deadline"] = deadline
|
|
124
|
-
if tags: params["tags"] = ",".join(tags)
|
|
125
|
-
if list_name: params["list"] = list_name
|
|
126
|
-
if heading: params["heading"] = heading
|
|
127
|
-
if checklist:
|
|
128
|
-
params["checklist-items"] = "\n".join(checklist)
|
|
129
|
-
|
|
130
|
-
encoded = urllib.parse.urlencode(params)
|
|
131
|
-
url = f"things:///add?{encoded}"
|
|
132
|
-
subprocess.run(["open", url])
|
|
133
|
-
return url
|
|
134
|
-
|
|
135
|
-
def add_project(title: str, notes: str = "", area: str = "", when: str = "") -> None:
|
|
136
|
-
"""Create a new project in Things 3."""
|
|
137
|
-
params = {"title": title}
|
|
138
|
-
if notes: params["notes"] = notes
|
|
139
|
-
if area: params["area"] = area
|
|
140
|
-
if when: params["when"] = when
|
|
141
|
-
encoded = urllib.parse.urlencode(params)
|
|
142
|
-
subprocess.run(["open", f"things:///add-project?{encoded}"])
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
## Usage Examples
|
|
146
|
-
|
|
147
|
-
```python
|
|
148
|
-
# Add a simple task
|
|
149
|
-
add_task("Buy groceries")
|
|
150
|
-
|
|
151
|
-
# Task with date and tags
|
|
152
|
-
add_task(
|
|
153
|
-
"Quarterly report",
|
|
154
|
-
notes="See shared doc in Drive",
|
|
155
|
-
when="2026-03-15",
|
|
156
|
-
deadline="2026-03-20",
|
|
157
|
-
tags=["work", "report"],
|
|
158
|
-
list_name="Q1 Planning"
|
|
159
|
-
)
|
|
160
|
-
|
|
161
|
-
# Task with checklist
|
|
162
|
-
add_task(
|
|
163
|
-
"Deploy v2.0",
|
|
164
|
-
checklist=["Run test suite", "Update CHANGELOG", "Tag release", "Deploy to prod", "Notify team"],
|
|
165
|
-
list_name="Engineering",
|
|
166
|
-
when="today"
|
|
167
|
-
)
|
|
168
|
-
|
|
169
|
-
# Read today's tasks
|
|
170
|
-
tasks = get_today()
|
|
171
|
-
print(f"📋 Today ({len(tasks)} tasks):")
|
|
172
|
-
for t in tasks:
|
|
173
|
-
print(f" □ {t['title']}")
|
|
174
|
-
|
|
175
|
-
# Read tasks in a project
|
|
176
|
-
tasks = get_tasks(area="Work")
|
|
177
|
-
for t in tasks:
|
|
178
|
-
print(f" □ {t['title']} [{t.get('project', 'no project')}]")
|
|
11
|
+
open "things:///add?title=Buy+groceries&when=today&tags=personal"
|
|
12
|
+
open "things:///add?title=Deploy+v2&when=2026-03-15&deadline=2026-03-20&list=Engineering"
|
|
13
|
+
open "things:///add-project?title=Q1+Planning¬es=..."
|
|
14
|
+
# Checklist items: &checklist-items=Step+1%0AStep+2%0AStep+3 (newline-separated, URL-encoded)
|
|
179
15
|
```
|
|
180
16
|
|
|
181
|
-
## `when`
|
|
17
|
+
## `when` values
|
|
182
18
|
|
|
183
19
|
| Value | Meaning |
|
|
184
20
|
|-------|---------|
|
|
@@ -189,11 +25,23 @@ for t in tasks:
|
|
|
189
25
|
| `someday` | Someday |
|
|
190
26
|
| `YYYY-MM-DD` | Specific date |
|
|
191
27
|
|
|
192
|
-
##
|
|
28
|
+
## Read tasks (SQLite - requires Full Disk Access)
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
THINGS_DB=$(find ~/Library/Group\ Containers -name "main.sqlite" -path "*Things*" 2>/dev/null | head -1)
|
|
32
|
+
sqlite3 "$THINGS_DB" "SELECT title, dueDate FROM TMTask WHERE status=0 AND trashed=0 AND type=0 ORDER BY dueDate ASC LIMIT 20;"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Workflow
|
|
36
|
+
|
|
37
|
+
1. **Adding tasks** → use the `things:///add` URL scheme (no permissions needed)
|
|
38
|
+
2. **Reading tasks** → query the SQLite database with `executeCommand`
|
|
39
|
+
3. **With checklists** → pass `checklist-items` URL param, newline-separated, URL-encoded
|
|
40
|
+
|
|
41
|
+
## Errors
|
|
193
42
|
|
|
194
43
|
| Error | Fix |
|
|
195
44
|
|-------|-----|
|
|
196
45
|
| DB not found | Open Things 3 first; try `find ~/Library -name "*.sqlite3" 2>/dev/null \| grep -i things` |
|
|
197
|
-
| Permission denied on DB | System Settings → Privacy
|
|
46
|
+
| Permission denied on DB | System Settings → Privacy → Full Disk Access → add Terminal |
|
|
198
47
|
| URL scheme not working | Ensure Things 3 is installed (`ls /Applications/Things3.app`) |
|
|
199
|
-
| Task not appearing | Things syncs on open; click "Things" in dock to bring to front |
|
package/skills/tmux.md
CHANGED
|
@@ -13,7 +13,7 @@ triggers: tmux, terminal session, background session, tmux window, tmux pane, sp
|
|
|
13
13
|
## Check tmux is Available
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
which tmux && tmux -V || echo "tmux not found
|
|
16
|
+
which tmux && tmux -V || echo "tmux not found - install with: brew install tmux"
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
## Session Management
|
|
@@ -71,51 +71,6 @@ tmux capture-pane -t mywork -p -S -500 > /tmp/session_output.txt
|
|
|
71
71
|
tmux capture-pane -t mywork -p | tail -5
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
-
## Python Helpers
|
|
75
|
-
|
|
76
|
-
```python
|
|
77
|
-
#!/usr/bin/env python3
|
|
78
|
-
import subprocess, time
|
|
79
|
-
|
|
80
|
-
def tmux_run(session: str, command: str, wait_seconds: float = 0.5) -> str:
|
|
81
|
-
"""Send a command to a tmux session and capture output."""
|
|
82
|
-
subprocess.run(["tmux", "send-keys", "-t", session, command, "Enter"])
|
|
83
|
-
time.sleep(wait_seconds)
|
|
84
|
-
result = subprocess.run(
|
|
85
|
-
["tmux", "capture-pane", "-t", session, "-p", "-S", "-100"],
|
|
86
|
-
capture_output=True, text=True
|
|
87
|
-
)
|
|
88
|
-
return result.stdout.strip()
|
|
89
|
-
|
|
90
|
-
def tmux_session_exists(session: str) -> bool:
|
|
91
|
-
result = subprocess.run(["tmux", "has-session", "-t", session], capture_output=True)
|
|
92
|
-
return result.returncode == 0
|
|
93
|
-
|
|
94
|
-
def tmux_get_output(session: str, lines: int = 50) -> str:
|
|
95
|
-
result = subprocess.run(
|
|
96
|
-
["tmux", "capture-pane", "-t", session, "-p", "-S", f"-{lines}"],
|
|
97
|
-
capture_output=True, text=True
|
|
98
|
-
)
|
|
99
|
-
return result.stdout.strip()
|
|
100
|
-
|
|
101
|
-
def tmux_ensure_session(session: str) -> None:
|
|
102
|
-
"""Create session if it doesn't exist."""
|
|
103
|
-
if not tmux_session_exists(session):
|
|
104
|
-
subprocess.run(["tmux", "new-session", "-d", "-s", session, "-x", "220", "-y", "50"])
|
|
105
|
-
print(f"Created tmux session: {session}")
|
|
106
|
-
else:
|
|
107
|
-
print(f"Using existing session: {session}")
|
|
108
|
-
|
|
109
|
-
# Example: run a dev server in tmux and monitor it
|
|
110
|
-
tmux_ensure_session("devserver")
|
|
111
|
-
output = tmux_run("devserver", "npm run dev", wait_seconds=2.0)
|
|
112
|
-
print(f"Server output:\n{output}")
|
|
113
|
-
|
|
114
|
-
# Later: check if server is still running
|
|
115
|
-
current = tmux_get_output("devserver", lines=10)
|
|
116
|
-
print(f"Current output:\n{current}")
|
|
117
|
-
```
|
|
118
|
-
|
|
119
74
|
## Split Panes (Parallel Work)
|
|
120
75
|
|
|
121
76
|
```bash
|
|
@@ -138,51 +93,6 @@ tmux split-window -v -t monitor:0.1
|
|
|
138
93
|
tmux send-keys -t monitor:0.2 "watch -n 2 df -h" Enter
|
|
139
94
|
```
|
|
140
95
|
|
|
141
|
-
## Monitor a Long-Running Task
|
|
142
|
-
|
|
143
|
-
```python
|
|
144
|
-
#!/usr/bin/env python3
|
|
145
|
-
"""Start a task in tmux and poll until it's done."""
|
|
146
|
-
import subprocess, time, re
|
|
147
|
-
|
|
148
|
-
def run_and_monitor(session: str, command: str, done_pattern: str, timeout: int = 300):
|
|
149
|
-
"""
|
|
150
|
-
Run command in tmux and wait until output matches done_pattern.
|
|
151
|
-
Returns final output.
|
|
152
|
-
"""
|
|
153
|
-
# Ensure session exists
|
|
154
|
-
subprocess.run(["tmux", "new-session", "-d", "-s", session, "-x", "220", "-y", "50"],
|
|
155
|
-
capture_output=True) # ignore error if exists
|
|
156
|
-
|
|
157
|
-
subprocess.run(["tmux", "send-keys", "-t", session, command, "Enter"])
|
|
158
|
-
print(f"Started: {command}")
|
|
159
|
-
|
|
160
|
-
start = time.time()
|
|
161
|
-
while time.time() - start < timeout:
|
|
162
|
-
time.sleep(3)
|
|
163
|
-
result = subprocess.run(
|
|
164
|
-
["tmux", "capture-pane", "-t", session, "-p", "-S", "-50"],
|
|
165
|
-
capture_output=True, text=True
|
|
166
|
-
)
|
|
167
|
-
output = result.stdout
|
|
168
|
-
if re.search(done_pattern, output, re.IGNORECASE):
|
|
169
|
-
print(f"✅ Done (matched: {done_pattern})")
|
|
170
|
-
return output
|
|
171
|
-
|
|
172
|
-
print(f"⚠️ Timed out after {timeout}s")
|
|
173
|
-
return subprocess.run(["tmux", "capture-pane", "-t", session, "-p", "-S", "-50"],
|
|
174
|
-
capture_output=True, text=True).stdout
|
|
175
|
-
|
|
176
|
-
# Example
|
|
177
|
-
output = run_and_monitor(
|
|
178
|
-
session="build",
|
|
179
|
-
command="npm run build",
|
|
180
|
-
done_pattern=r"build complete|error|failed",
|
|
181
|
-
timeout=120
|
|
182
|
-
)
|
|
183
|
-
print(output[-500:]) # show last 500 chars
|
|
184
|
-
```
|
|
185
|
-
|
|
186
96
|
## Useful Shortcuts Reference
|
|
187
97
|
|
|
188
98
|
```
|
package/skills/trello.md
CHANGED
|
@@ -1,183 +1,58 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: trello
|
|
3
|
-
description: Manage Trello boards, lists, and cards
|
|
3
|
+
description: Manage Trello boards, lists, and cards via the Trello REST API. Use when the user asks to create a Trello card, move a card, list boards, add a checklist, or automate Trello workflows. Requires TRELLO_API_KEY and TRELLO_TOKEN.
|
|
4
4
|
triggers: trello, kanban, trello card, trello board, create card, move card, trello list, trello checklist, trello archive, trello label
|
|
5
|
+
metadata: {"daemora": {"emoji": "📋"}}
|
|
5
6
|
---
|
|
6
7
|
|
|
7
|
-
## When to Use
|
|
8
|
-
|
|
9
|
-
✅ Create/update/move/archive cards, list boards and cards, add checklists, labels, due dates, comments, attach files, automate Trello workflows
|
|
10
|
-
|
|
11
|
-
❌ Real-time push notifications (use Trello webhooks + a server endpoint) — use polling for monitoring instead
|
|
12
|
-
|
|
13
8
|
## Setup
|
|
14
9
|
|
|
15
|
-
|
|
16
|
-
# Get your API key: https://trello.com/app-key
|
|
17
|
-
# Generate token from that page
|
|
10
|
+
Get API key: https://trello.com/app-key (then generate token from that page)
|
|
18
11
|
|
|
12
|
+
```bash
|
|
19
13
|
export TRELLO_API_KEY="your_api_key"
|
|
20
14
|
export TRELLO_TOKEN="your_token"
|
|
21
15
|
|
|
22
|
-
# Quick test
|
|
23
|
-
curl -s "https://api.trello.com/1/members/me/boards?key=$
|
|
16
|
+
# Quick test: list boards
|
|
17
|
+
curl -s "https://api.trello.com/1/members/me/boards?fields=name,id&key=$TRELLO_API_KEY&token=$TRELLO_TOKEN" \
|
|
24
18
|
| python3 -c "import sys,json; [print(b['name'], '→', b['id']) for b in json.load(sys.stdin)]"
|
|
25
19
|
```
|
|
26
20
|
|
|
27
|
-
##
|
|
21
|
+
## Key API endpoints
|
|
28
22
|
|
|
29
|
-
|
|
30
|
-
#!/usr/bin/env python3
|
|
31
|
-
import os, json, urllib.request, urllib.parse
|
|
23
|
+
Base: `https://api.trello.com/1` - always append `key=$TRELLO_API_KEY&token=$TRELLO_TOKEN`
|
|
32
24
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
25
|
+
| Operation | Method | Endpoint |
|
|
26
|
+
|-----------|--------|----------|
|
|
27
|
+
| List boards | GET | `/members/me/boards?fields=name,id` |
|
|
28
|
+
| Lists on board | GET | `/boards/{id}/lists?filter=open` |
|
|
29
|
+
| Cards on board | GET | `/boards/{id}/cards?fields=name,idList,due` |
|
|
30
|
+
| Cards in list | GET | `/lists/{id}/cards` |
|
|
31
|
+
| Create card | POST | `/cards` |
|
|
32
|
+
| Move card | PUT | `/cards/{id}` with `idList` param |
|
|
33
|
+
| Archive card | PUT | `/cards/{id}` with `closed=true` |
|
|
34
|
+
| Add comment | POST | `/cards/{id}/actions/comments` |
|
|
35
|
+
| Add checklist | POST | `/checklists` with `idCard` param |
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
"""Make a Trello API call. kwargs become query params for GET, form data for POST/PUT."""
|
|
39
|
-
params = {"key": API_KEY, "token": TOKEN, **kwargs}
|
|
40
|
-
url = f"{BASE}{path}"
|
|
41
|
-
if method == "GET":
|
|
42
|
-
url += "?" + urllib.parse.urlencode(params)
|
|
43
|
-
req = urllib.request.Request(url)
|
|
44
|
-
else:
|
|
45
|
-
data = urllib.parse.urlencode(params).encode()
|
|
46
|
-
req = urllib.request.Request(url, data=data, method=method)
|
|
47
|
-
with urllib.request.urlopen(req) as resp:
|
|
48
|
-
return json.loads(resp.read())
|
|
49
|
-
```
|
|
37
|
+
## Create card example
|
|
50
38
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
open_boards = [b for b in boards if not b.get("closed")]
|
|
56
|
-
for b in open_boards:
|
|
57
|
-
print(f"📋 {b['name']} (id: {b['id']})")
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
## Get Lists on a Board
|
|
61
|
-
|
|
62
|
-
```python
|
|
63
|
-
board_id = "BOARD_ID_HERE"
|
|
64
|
-
lists = trello("GET", f"/boards/{board_id}/lists", filter="open")
|
|
65
|
-
for lst in lists:
|
|
66
|
-
print(f" 📁 {lst['name']} (id: {lst['id']})")
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
## Get Cards on a Board / List
|
|
70
|
-
|
|
71
|
-
```python
|
|
72
|
-
# All cards on a board
|
|
73
|
-
board_id = "BOARD_ID_HERE"
|
|
74
|
-
cards = trello("GET", f"/boards/{board_id}/cards", fields="name,idList,due,desc,labels")
|
|
75
|
-
for card in cards:
|
|
76
|
-
print(f" 🃏 {card['name']} (due: {card.get('due', 'none')})")
|
|
77
|
-
|
|
78
|
-
# Cards in a specific list
|
|
79
|
-
list_id = "LIST_ID_HERE"
|
|
80
|
-
cards = trello("GET", f"/lists/{list_id}/cards", fields="name,due,desc")
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
## Create a Card
|
|
84
|
-
|
|
85
|
-
```python
|
|
86
|
-
new_card = trello("POST", "/cards",
|
|
87
|
-
idList="LIST_ID_HERE",
|
|
88
|
-
name="Task: Fix login bug",
|
|
89
|
-
desc="## Description\nThe login form fails when email contains special characters.\n\n## Steps\n1. Go to /login\n2. Enter: user+test@example.com\n3. See error",
|
|
90
|
-
due="2026-03-15T09:00:00.000Z", # ISO 8601
|
|
91
|
-
pos="top" # or "bottom" or a position number
|
|
92
|
-
)
|
|
93
|
-
print(f"✅ Card created: {new_card['name']} → {new_card['url']}")
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
## Move Card to Another List
|
|
97
|
-
|
|
98
|
-
```python
|
|
99
|
-
card_id = "CARD_ID_HERE"
|
|
100
|
-
target_list_id = "TARGET_LIST_ID"
|
|
101
|
-
trello("PUT", f"/cards/{card_id}", idList=target_list_id)
|
|
102
|
-
print("✅ Card moved")
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
## Add Checklist to Card
|
|
106
|
-
|
|
107
|
-
```python
|
|
108
|
-
card_id = "CARD_ID_HERE"
|
|
109
|
-
|
|
110
|
-
# Create checklist
|
|
111
|
-
checklist = trello("POST", "/checklists", idCard=card_id, name="Acceptance Criteria")
|
|
112
|
-
|
|
113
|
-
# Add items
|
|
114
|
-
for item in ["Tests pass", "Code reviewed", "Deployed to staging", "QA sign-off"]:
|
|
115
|
-
trello("POST", f"/checklists/{checklist['id']}/checkItems", name=item)
|
|
116
|
-
|
|
117
|
-
print(f"✅ Checklist created with {4} items")
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
## Add Comment to Card
|
|
121
|
-
|
|
122
|
-
```python
|
|
123
|
-
card_id = "CARD_ID_HERE"
|
|
124
|
-
trello("POST", f"/cards/{card_id}/actions/comments",
|
|
125
|
-
text="Investigated this issue — root cause is in auth.js:142. PR coming shortly.")
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
## Add Label to Card
|
|
129
|
-
|
|
130
|
-
```python
|
|
131
|
-
# First get or create a label
|
|
132
|
-
board_id = "BOARD_ID_HERE"
|
|
133
|
-
labels = trello("GET", f"/boards/{board_id}/labels")
|
|
134
|
-
bug_label = next((l for l in labels if l.get("name") == "Bug"), None)
|
|
135
|
-
|
|
136
|
-
if bug_label:
|
|
137
|
-
card_id = "CARD_ID_HERE"
|
|
138
|
-
trello("POST", f"/cards/{card_id}/idLabels", value=bug_label["id"])
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
## Archive (Close) a Card
|
|
142
|
-
|
|
143
|
-
```python
|
|
144
|
-
card_id = "CARD_ID_HERE"
|
|
145
|
-
trello("PUT", f"/cards/{card_id}", closed="true")
|
|
146
|
-
print("✅ Card archived")
|
|
39
|
+
```bash
|
|
40
|
+
curl -s -X POST "https://api.trello.com/1/cards" \
|
|
41
|
+
-d "key=$TRELLO_API_KEY&token=$TRELLO_TOKEN" \
|
|
42
|
+
-d "idList=LIST_ID&name=Task+title&desc=Description&due=2026-03-15T09:00:00.000Z&pos=top"
|
|
147
43
|
```
|
|
148
44
|
|
|
149
|
-
##
|
|
150
|
-
|
|
151
|
-
```python
|
|
152
|
-
def add_task_to_trello(title: str, description: str, board_name: str = None, list_name: str = "To Do"):
|
|
153
|
-
"""Find the right board/list and create a card."""
|
|
154
|
-
boards = trello("GET", "/members/me/boards", fields="name,id")
|
|
155
|
-
board = next((b for b in boards if board_name and board_name.lower() in b["name"].lower()), boards[0])
|
|
156
|
-
|
|
157
|
-
lists = trello("GET", f"/boards/{board['id']}/lists", filter="open")
|
|
158
|
-
target_list = next((l for l in lists if list_name.lower() in l["name"].lower()), lists[0])
|
|
45
|
+
## Move card example
|
|
159
46
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
desc=description,
|
|
164
|
-
pos="top"
|
|
165
|
-
)
|
|
166
|
-
return card["url"]
|
|
167
|
-
|
|
168
|
-
url = add_task_to_trello(
|
|
169
|
-
"Research competitor pricing",
|
|
170
|
-
"## Goal\nAnalyze top 5 competitor pricing pages\n\n## Deliverable\nComparison spreadsheet",
|
|
171
|
-
board_name="Marketing"
|
|
172
|
-
)
|
|
173
|
-
print(f"Card created: {url}")
|
|
47
|
+
```bash
|
|
48
|
+
curl -s -X PUT "https://api.trello.com/1/cards/CARD_ID" \
|
|
49
|
+
-d "key=$TRELLO_API_KEY&token=$TRELLO_TOKEN&idList=TARGET_LIST_ID"
|
|
174
50
|
```
|
|
175
51
|
|
|
176
|
-
##
|
|
52
|
+
## Errors
|
|
177
53
|
|
|
178
54
|
| Error | Fix |
|
|
179
55
|
|-------|-----|
|
|
180
|
-
| 401 Unauthorized | Check `TRELLO_API_KEY` and `TRELLO_TOKEN` are
|
|
181
|
-
| 404 Not Found | Board/list/card ID is wrong
|
|
182
|
-
| 429 Rate Limited |
|
|
183
|
-
| Card won't move | Verify target list is on same board as card |
|
|
56
|
+
| 401 Unauthorized | Check `TRELLO_API_KEY` and `TRELLO_TOKEN` are valid |
|
|
57
|
+
| 404 Not Found | Board/list/card ID is wrong - list boards/lists first to get correct IDs |
|
|
58
|
+
| 429 Rate Limited | Add 100ms delay between bulk calls |
|