Skip to content

Commit fcdafca

Browse files
committed
feat: Tier 3-4 — communication channels & productivity skills
Communication channels: - Telegram bot (long polling, no external deps, net/http only) - Bidirectional messaging with typing indicators - Chat ID allowlist for security - Auto-retry on Markdown parse failures - Message chunking for >4096 char responses - /start and /health commands - Webhook endpoint (POST /api/webhooks) - HMAC-SHA256 signature verification (GitHub compatible) - Sync and async modes - Plain text fallback for non-JSON payloads - Email notifications (net/smtp, no external deps) - SMTP sender with configurable host/port/auth - send_email skill for agent-initiated emails Productivity skills: - Task management: task_add, task_list, task_update, task_delete - JSON file storage, priority sorting, tag filtering - Status tracking (todo/in_progress/done) - Note-taking/PKM: note_save, note_read, note_list, note_delete, note_search - Markdown files in data/notes/ - Full-text search with snippet extraction - Path traversal protection via sanitization Config additions: - WebhookChannelConfig (enabled, HMAC secret) - EmailConfig (SMTP host/port/username/password/from) - Env var resolution for webhook secret and email password Zero new external dependencies — all implemented with Go stdlib.
1 parent affa876 commit fcdafca

9 files changed

Lines changed: 1427 additions & 2 deletions

File tree

cmd/pennyclaw/main.go

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ import (
1212
"syscall"
1313

1414
"github.com/mandarl/pennyclaw/internal/agent"
15+
"github.com/mandarl/pennyclaw/internal/channels/telegram"
1516
"github.com/mandarl/pennyclaw/internal/channels/web"
17+
"github.com/mandarl/pennyclaw/internal/channels/webhook"
1618
"github.com/mandarl/pennyclaw/internal/config"
1719
)
1820

@@ -74,22 +76,49 @@ func main() {
7476
log.Fatalf("Failed to initialize agent: %v", err)
7577
}
7678

79+
// Create webhook handler (used by web server if enabled)
80+
var webhookHandler *webhook.Handler
81+
if cfg.Channels.Webhook.Enabled {
82+
webhookHandler = webhook.New(webhook.Config{
83+
Secret: cfg.Channels.Webhook.Secret,
84+
}, ag.HandleMessage)
85+
log.Println("Webhook endpoint enabled at /api/webhooks")
86+
}
87+
7788
// Start web server
7889
srv := web.NewServer(cfg.Server.Host, cfg.Server.Port, ag.HandleMessage, cfg, *configPath,
79-
ag.Memory(), version, ag.Workspace(), ag.Scheduler(), ag.SkillPack())
90+
ag.Memory(), version, ag.Workspace(), ag.Scheduler(), ag.SkillPack(), webhookHandler)
8091
go func() {
8192
log.Printf("PennyClaw %s starting on %s:%d", version, cfg.Server.Host, cfg.Server.Port)
8293
if err := srv.Start(); err != nil {
8394
log.Fatalf("Web server error: %v", err)
8495
}
8596
}()
8697

98+
// Start Telegram bot if configured
99+
var tgBot *telegram.Bot
100+
if cfg.Channels.Telegram.Enabled && cfg.Channels.Telegram.Token != "" {
101+
tgBot, err = telegram.New(telegram.Config{
102+
Token: cfg.Channels.Telegram.Token,
103+
}, ag.HandleMessage)
104+
if err != nil {
105+
log.Printf("Warning: failed to create Telegram bot: %v", err)
106+
} else {
107+
if err := tgBot.Start(); err != nil {
108+
log.Printf("Warning: failed to start Telegram bot: %v", err)
109+
}
110+
}
111+
}
112+
87113
// Graceful shutdown
88114
sigCh := make(chan os.Signal, 1)
89115
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
90116
<-sigCh
91117

92118
log.Println("Shutting down PennyClaw...")
119+
if tgBot != nil {
120+
tgBot.Stop()
121+
}
93122
ag.Stop()
94123
srv.Stop()
95124
log.Println("Goodbye!")

internal/agent/agent.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/mandarl/pennyclaw/internal/cron"
1515
"github.com/mandarl/pennyclaw/internal/llm"
1616
"github.com/mandarl/pennyclaw/internal/memory"
17+
"github.com/mandarl/pennyclaw/internal/notify"
1718
"github.com/mandarl/pennyclaw/internal/sandbox"
1819
"github.com/mandarl/pennyclaw/internal/skillpack"
1920
"github.com/mandarl/pennyclaw/internal/skills"
@@ -110,6 +111,24 @@ func New(cfg *config.Config, dataDir string) (*Agent, error) {
110111
// Register workspace skills
111112
agent.registerWorkspaceSkills()
112113

114+
// Register productivity skills (tasks, notes)
115+
skills.RegisterProductivitySkills(skillRegistry, dataDir)
116+
117+
// Register email skill if configured
118+
var emailNotifier *notify.EmailNotifier
119+
if cfg.Email.Enabled {
120+
emailNotifier = notify.NewEmailNotifier(notify.EmailConfig{
121+
SMTPHost: cfg.Email.SMTPHost,
122+
SMTPPort: cfg.Email.SMTPPort,
123+
Username: cfg.Email.Username,
124+
Password: cfg.Email.Password,
125+
FromAddress: cfg.Email.FromAddress,
126+
FromName: cfg.Email.FromName,
127+
})
128+
skills.RegisterEmailSkill(skillRegistry, emailNotifier)
129+
log.Printf("Email notifications enabled (SMTP: %s)", cfg.Email.SMTPHost)
130+
}
131+
113132
// Initialize cron scheduler (uses same SQLite DB)
114133
scheduler, err := cron.NewScheduler(mem.DB(), agent.HandleMessage)
115134
if err != nil {

0 commit comments

Comments
 (0)