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.
Files changed (134) hide show
  1. package/README.md +106 -76
  2. package/SOUL.md +100 -28
  3. package/config/mcp.json +9 -9
  4. package/package.json +15 -8
  5. package/skills/apple-notes.md +0 -52
  6. package/skills/apple-reminders.md +1 -87
  7. package/skills/camsnap.md +20 -144
  8. package/skills/coding.md +7 -7
  9. package/skills/documents.md +6 -6
  10. package/skills/email.md +6 -6
  11. package/skills/gif-search.md +28 -171
  12. package/skills/healthcheck.md +21 -203
  13. package/skills/image-gen.md +24 -123
  14. package/skills/model-usage.md +18 -165
  15. package/skills/obsidian.md +28 -174
  16. package/skills/pdf.md +30 -181
  17. package/skills/research.md +6 -6
  18. package/skills/skill-creator.md +35 -111
  19. package/skills/spotify.md +2 -17
  20. package/skills/summarize.md +36 -193
  21. package/skills/things.md +23 -175
  22. package/skills/tmux.md +1 -91
  23. package/skills/trello.md +32 -157
  24. package/skills/video-frames.md +26 -166
  25. package/skills/weather.md +6 -6
  26. package/src/a2a/A2AClient.js +2 -2
  27. package/src/a2a/A2AServer.js +6 -6
  28. package/src/a2a/AgentCard.js +2 -2
  29. package/src/agents/SubAgentManager.js +61 -19
  30. package/src/agents/Supervisor.js +4 -4
  31. package/src/channels/BaseChannel.js +6 -6
  32. package/src/channels/BlueBubblesChannel.js +112 -0
  33. package/src/channels/DiscordChannel.js +8 -8
  34. package/src/channels/EmailChannel.js +54 -26
  35. package/src/channels/FeishuChannel.js +140 -0
  36. package/src/channels/GoogleChatChannel.js +8 -8
  37. package/src/channels/HttpChannel.js +2 -2
  38. package/src/channels/IRCChannel.js +144 -0
  39. package/src/channels/LineChannel.js +13 -13
  40. package/src/channels/MatrixChannel.js +97 -0
  41. package/src/channels/MattermostChannel.js +119 -0
  42. package/src/channels/NextcloudChannel.js +133 -0
  43. package/src/channels/NostrChannel.js +175 -0
  44. package/src/channels/SignalChannel.js +9 -9
  45. package/src/channels/SlackChannel.js +10 -10
  46. package/src/channels/TeamsChannel.js +10 -10
  47. package/src/channels/TelegramChannel.js +8 -8
  48. package/src/channels/TwitchChannel.js +128 -0
  49. package/src/channels/WhatsAppChannel.js +10 -10
  50. package/src/channels/ZaloChannel.js +119 -0
  51. package/src/channels/iMessageChannel.js +150 -0
  52. package/src/channels/index.js +241 -11
  53. package/src/cli.js +835 -38
  54. package/src/config/agentProfiles.js +19 -19
  55. package/src/config/channels.js +1 -1
  56. package/src/config/default.js +12 -7
  57. package/src/config/models.js +3 -3
  58. package/src/config/permissions.js +2 -2
  59. package/src/core/AgentLoop.js +13 -13
  60. package/src/core/Compaction.js +3 -3
  61. package/src/core/CostTracker.js +2 -2
  62. package/src/core/EventBus.js +15 -15
  63. package/src/core/TaskQueue.js +24 -7
  64. package/src/core/TaskRunner.js +19 -6
  65. package/src/daemon/DaemonManager.js +4 -4
  66. package/src/hooks/HookRunner.js +4 -4
  67. package/src/index.js +6 -2
  68. package/src/mcp/MCPAgentRunner.js +3 -3
  69. package/src/mcp/MCPClient.js +9 -9
  70. package/src/mcp/MCPManager.js +14 -14
  71. package/src/models/ModelRouter.js +2 -2
  72. package/src/safety/AuditLog.js +3 -3
  73. package/src/safety/CircuitBreaker.js +2 -2
  74. package/src/safety/CommandGuard.js +132 -0
  75. package/src/safety/FilesystemGuard.js +23 -3
  76. package/src/safety/GitRollback.js +5 -5
  77. package/src/safety/HumanApproval.js +9 -9
  78. package/src/safety/InputSanitizer.js +81 -8
  79. package/src/safety/PermissionGuard.js +2 -2
  80. package/src/safety/Sandbox.js +1 -1
  81. package/src/safety/SecretScanner.js +90 -28
  82. package/src/safety/SecretVault.js +2 -2
  83. package/src/scheduler/Heartbeat.js +3 -3
  84. package/src/scheduler/Scheduler.js +6 -6
  85. package/src/setup/theme.js +171 -66
  86. package/src/setup/wizard.js +432 -57
  87. package/src/skills/SkillLoader.js +145 -8
  88. package/src/storage/TaskStore.js +39 -15
  89. package/src/systemPrompt.js +45 -43
  90. package/src/tenants/TenantManager.js +79 -22
  91. package/src/tools/ToolRegistry.js +3 -3
  92. package/src/tools/applyPatch.js +2 -2
  93. package/src/tools/browserAutomation.js +4 -4
  94. package/src/tools/calendar.js +155 -0
  95. package/src/tools/clipboard.js +71 -0
  96. package/src/tools/contacts.js +138 -0
  97. package/src/tools/createDocument.js +2 -2
  98. package/src/tools/cronTool.js +14 -14
  99. package/src/tools/database.js +165 -0
  100. package/src/tools/editFile.js +10 -10
  101. package/src/tools/executeCommand.js +11 -3
  102. package/src/tools/generateImage.js +79 -0
  103. package/src/tools/gitTool.js +141 -0
  104. package/src/tools/glob.js +1 -1
  105. package/src/tools/googlePlaces.js +136 -0
  106. package/src/tools/grep.js +2 -2
  107. package/src/tools/iMessageTool.js +86 -0
  108. package/src/tools/imageAnalysis.js +3 -3
  109. package/src/tools/index.js +56 -2
  110. package/src/tools/makeVoiceCall.js +283 -0
  111. package/src/tools/manageAgents.js +2 -2
  112. package/src/tools/manageMCP.js +38 -20
  113. package/src/tools/memory.js +25 -32
  114. package/src/tools/messageChannel.js +1 -1
  115. package/src/tools/notification.js +90 -0
  116. package/src/tools/philipsHue.js +147 -0
  117. package/src/tools/projectTracker.js +8 -8
  118. package/src/tools/readFile.js +1 -1
  119. package/src/tools/readPDF.js +73 -0
  120. package/src/tools/screenCapture.js +6 -6
  121. package/src/tools/searchContent.js +2 -2
  122. package/src/tools/searchFiles.js +1 -1
  123. package/src/tools/sendEmail.js +79 -24
  124. package/src/tools/sendFile.js +4 -4
  125. package/src/tools/sonos.js +137 -0
  126. package/src/tools/sshTool.js +130 -0
  127. package/src/tools/textToSpeech.js +5 -5
  128. package/src/tools/transcribeAudio.js +4 -4
  129. package/src/tools/useMCP.js +4 -4
  130. package/src/tools/webFetch.js +2 -2
  131. package/src/tools/webSearch.js +1 -1
  132. package/src/utils/Embeddings.js +79 -0
  133. package/src/voice/VoiceSessionManager.js +170 -0
  134. package/src/voice/VoiceWebhook.js +188 -0
@@ -31,42 +31,113 @@ export async function runSetupWizard() {
31
31
  const provider = guard(await p.select({
32
32
  message: "Which AI provider?",
33
33
  options: [
34
- { value: "openai", label: "OpenAI", hint: "GPT-4.1 \u2014 best all-rounder" },
35
- { value: "anthropic", label: "Anthropic", hint: "Claude \u2014 great for coding & reasoning" },
36
- { value: "google", label: "Google AI", hint: "Gemini \u2014 fast & capable" },
37
- { value: "ollama", label: "Ollama", hint: "Local models \u2014 free, private" },
34
+ { value: "openai", label: "OpenAI", hint: "GPT-5 / GPT-4.1 \u2014 best all-rounder" },
35
+ { value: "anthropic", label: "Anthropic", hint: "Claude 4 \u2014 great for coding & reasoning" },
36
+ { value: "google", label: "Google AI", hint: "Gemini 3.1 / 2.5 \u2014 fast & capable" },
37
+ { value: "xai", label: "xAI", hint: "Grok 4 \u2014 conversational & capable" },
38
+ { value: "deepseek", label: "DeepSeek", hint: "DeepSeek V3 / R1 \u2014 excellent coder, cheap" },
39
+ { value: "mistral", label: "Mistral", hint: "Mistral Large \u2014 European AI, GDPR-friendly" },
40
+ { value: "ollama", label: "Ollama", hint: "Local models \u2014 free, private, offline" },
38
41
  ],
39
42
  }));
40
43
 
41
44
  if (provider === "openai") {
42
- const key = guard(await p.password({ message: "OpenAI API key", validate: (v) => !v ? "Required" : undefined }));
45
+ const key = guard(await p.password({ message: "OpenAI API key (sk-...)", validate: (v) => !v ? "Required" : undefined }));
43
46
  envConfig.OPENAI_API_KEY = key;
44
47
  envConfig.DEFAULT_MODEL = guard(await p.select({
45
48
  message: "OpenAI model",
46
49
  options: [
47
- { value: "openai:gpt-4.1-mini", label: "gpt-4.1-mini", hint: "Fast & cheap (recommended)" },
48
- { value: "openai:gpt-4.1", label: "gpt-4.1", hint: "Most capable" },
49
- { value: "openai:gpt-4o-mini", label: "gpt-4o-mini", hint: "Balanced" },
50
+ { value: "openai:gpt-4.1-mini", label: "gpt-4.1-mini", hint: "1M ctx \u2014 fast & affordable (recommended)" },
51
+ { value: "openai:gpt-5.2-pro", label: "gpt-5.2-pro", hint: "GPT-5.2 Pro \u2014 highest capability [NEW]" },
52
+ { value: "openai:gpt-5.2", label: "gpt-5.2", hint: "GPT-5.2 flagship (Dec 2025) [NEW]" },
53
+ { value: "openai:gpt-5", label: "gpt-5", hint: "GPT-5 flagship (Aug 2025)" },
54
+ { value: "openai:gpt-5-mini", label: "gpt-5-mini", hint: "GPT-5 Mini \u2014 fast & cheap" },
55
+ { value: "openai:gpt-4.1", label: "gpt-4.1", hint: "1M ctx, best instruction following" },
56
+ { value: "openai:gpt-4.1-nano", label: "gpt-4.1-nano", hint: "1M ctx, cheapest" },
57
+ { value: "openai:o3-pro", label: "o3-pro", hint: "Best reasoning \u2014 most thorough" },
58
+ { value: "openai:o4-mini", label: "o4-mini", hint: "Fast reasoning (Apr 2025)" },
59
+ { value: "openai:gpt-4o", label: "gpt-4o", hint: "Vision + text (128K ctx)" },
60
+ { value: "openai:gpt-4o-mini", label: "gpt-4o-mini", hint: "GPT-4o Mini \u2014 balanced" },
50
61
  ],
51
62
  }));
52
63
  } else if (provider === "anthropic") {
53
- const key = guard(await p.password({ message: "Anthropic API key", validate: (v) => !v ? "Required" : undefined }));
64
+ const key = guard(await p.password({ message: "Anthropic API key (sk-ant-...)", validate: (v) => !v ? "Required" : undefined }));
54
65
  envConfig.ANTHROPIC_API_KEY = key;
55
66
  envConfig.DEFAULT_MODEL = guard(await p.select({
56
67
  message: "Claude model",
57
68
  options: [
58
- { value: "anthropic:claude-sonnet-4-6", label: "claude-sonnet-4-6", hint: "Fast & smart (recommended)" },
59
- { value: "anthropic:claude-opus-4-6", label: "claude-opus-4-6", hint: "Most capable" },
60
- { value: "anthropic:claude-haiku-4-5-20251001", label: "claude-haiku-4-5", hint: "Fastest & cheapest" },
69
+ { value: "anthropic:claude-sonnet-4-6", label: "claude-sonnet-4-6", hint: "Best speed/intelligence \u2014 coding & agents [NEW]" },
70
+ { value: "anthropic:claude-opus-4-6", label: "claude-opus-4-6", hint: "Most intelligent \u2014 extended thinking [NEW]" },
71
+ { value: "anthropic:claude-haiku-4-5", label: "claude-haiku-4-5", hint: "Fastest \u2014 high-volume tasks" },
72
+ { value: "anthropic:claude-sonnet-4-5-20250929", label: "claude-sonnet-4-5-20250929", hint: "Sonnet 4.5 \u2014 coding & agentic (200K ctx)" },
73
+ { value: "anthropic:claude-3-5-sonnet-latest", label: "claude-3-5-sonnet-latest", hint: "3.5 Sonnet \u2014 widely used previous gen" },
61
74
  ],
62
75
  }));
63
76
  } else if (provider === "google") {
64
77
  const key = guard(await p.password({ message: "Google AI API key", validate: (v) => !v ? "Required" : undefined }));
65
78
  envConfig.GOOGLE_AI_API_KEY = key;
66
- envConfig.DEFAULT_MODEL = "google:gemini-2.0-flash";
79
+ envConfig.DEFAULT_MODEL = guard(await p.select({
80
+ message: "Gemini model",
81
+ options: [
82
+ { value: "google:gemini-2.5-flash", label: "gemini-2.5-flash", hint: "Fast & cost-effective \u2014 recommended" },
83
+ { value: "google:gemini-3.1-pro-preview", label: "gemini-3.1-pro-preview", hint: "Latest \u2014 complex tasks [NEW]" },
84
+ { value: "google:gemini-3.1-flash-lite-preview", label: "gemini-3.1-flash-lite-preview", hint: "Latest lite \u2014 cost-efficient [NEW]" },
85
+ { value: "google:gemini-2.5-pro", label: "gemini-2.5-pro", hint: "Complex reasoning & coding (1M ctx)" },
86
+ { value: "google:gemini-2.5-flash-lite", label: "gemini-2.5-flash-lite", hint: "Speed-optimised high-throughput" },
87
+ { value: "google:gemini-2.0-flash", label: "gemini-2.0-flash", hint: "Previous gen flash" },
88
+ ],
89
+ }));
90
+ } else if (provider === "xai") {
91
+ const key = guard(await p.password({ message: "xAI API key", validate: (v) => !v ? "Required" : undefined }));
92
+ envConfig.XAI_API_KEY = key;
93
+ envConfig.DEFAULT_MODEL = guard(await p.select({
94
+ message: "Grok model",
95
+ options: [
96
+ { value: "xai:grok-4", label: "grok-4", hint: "Latest & most capable (Jul 2025) [NEW]" },
97
+ { value: "xai:grok-3-beta", label: "grok-3-beta", hint: "Grok 3 Beta \u2014 131K ctx" },
98
+ { value: "xai:grok-3-mini-beta", label: "grok-3-mini-beta", hint: "Grok 3 Mini \u2014 fast, 131K ctx" },
99
+ ],
100
+ }));
101
+ } else if (provider === "deepseek") {
102
+ const key = guard(await p.password({ message: "DeepSeek API key (sk-...)", validate: (v) => !v ? "Required" : undefined }));
103
+ envConfig.DEEPSEEK_API_KEY = key;
104
+ envConfig.DEFAULT_MODEL = guard(await p.select({
105
+ message: "DeepSeek model",
106
+ options: [
107
+ { value: "deepseek:deepseek-chat", label: "deepseek-chat", hint: "V3 \u2014 excellent coder (128K ctx, recommended)" },
108
+ { value: "deepseek:deepseek-reasoner", label: "deepseek-reasoner", hint: "R1 \u2014 chain-of-thought reasoning" },
109
+ ],
110
+ }));
111
+ } else if (provider === "mistral") {
112
+ const key = guard(await p.password({ message: "Mistral API key", validate: (v) => !v ? "Required" : undefined }));
113
+ envConfig.MISTRAL_API_KEY = key;
114
+ envConfig.DEFAULT_MODEL = guard(await p.select({
115
+ message: "Mistral model",
116
+ options: [
117
+ { value: "mistral:mistral-large-2512", label: "mistral-large-2512", hint: "Flagship \u2014 best quality (Dec 2025) [NEW]" },
118
+ { value: "mistral:mistral-medium-3", label: "mistral-medium-3", hint: "Balanced capability & speed" },
119
+ { value: "mistral:codestral-2508", label: "codestral-2508", hint: "Code specialist (Aug 2025)" },
120
+ { value: "mistral:mistral-small-3.2-24b", label: "mistral-small-3.2-24b", hint: "Lightweight, runs locally (24B)" },
121
+ ],
122
+ }));
67
123
  } else if (provider === "ollama") {
68
- p.note("Make sure Ollama is running: ollama serve", "Ollama");
69
- const model = guard(await p.text({ message: "Ollama model name", initialValue: "llama3" }));
124
+ p.note(
125
+ [
126
+ "Make sure Ollama is running: ollama serve",
127
+ "Pull a model first: ollama pull llama4-maverick",
128
+ "Recommended models:",
129
+ " llama4-maverick \u2014 Llama 4, 17B MoE, multimodal, 1M ctx",
130
+ " llama4-scout \u2014 Llama 4, 17B MoE, 10M ctx",
131
+ " llama3.3 \u2014 best 70B open model",
132
+ " qwen2.5 \u2014 strong coder",
133
+ ].join("\n"),
134
+ "Ollama (local models)",
135
+ );
136
+ const model = guard(await p.text({
137
+ message: "Ollama model name",
138
+ initialValue: "llama4-maverick",
139
+ placeholder: "e.g. llama4-maverick, llama3.3, qwen2.5",
140
+ }));
70
141
  envConfig.DEFAULT_MODEL = `ollama:${model}`;
71
142
  }
72
143
 
@@ -109,11 +180,11 @@ export async function runSetupWizard() {
109
180
  p.note(
110
181
  [
111
182
  "Control which directories the agent can read/write.",
112
- "Works like Docker volume mounts the agent cannot escape",
183
+ "Works like Docker volume mounts - the agent cannot escape",
113
184
  "the directories you allow.",
114
185
  "",
115
- ` ${S.arrow} ${t.accent("Global")} agent accesses any file the OS allows (default)`,
116
- ` ${S.arrow} ${t.accent("Scoped")} agent locked to specific directories only`,
186
+ ` ${S.arrow} ${t.accent("Global")} - agent accesses any file the OS allows (default)`,
187
+ ` ${S.arrow} ${t.accent("Scoped")} - agent locked to specific directories only`,
117
188
  "",
118
189
  "Sensitive system files (.ssh, .env, /etc/shadow, etc.)",
119
190
  "are always blocked regardless of this setting.",
@@ -182,24 +253,59 @@ export async function runSetupWizard() {
182
253
  p.log.info(`HTTP API is always enabled on port ${t.bold(port)}`);
183
254
  p.log.info(`Press ${t.bold("space")} to select, ${t.bold("enter")} to confirm`);
184
255
 
256
+ p.note(
257
+ [
258
+ "Select every channel you want to activate.",
259
+ "Each selected channel will ask for its credentials.",
260
+ "You can add more channels later by editing your .env file.",
261
+ "",
262
+ "Tip: each channel supports an optional allowlist (restrict",
263
+ " who can message the agent) and a model override.",
264
+ " Configure those later with: daemora tenant set",
265
+ ].join("\n"),
266
+ "Channels"
267
+ );
268
+
185
269
  const channels = guard(await p.multiselect({
186
- message: "Enable additional channels",
270
+ message: "Enable channels (space = toggle, enter = confirm)",
187
271
  options: [
188
- { value: "telegram", label: "Telegram", hint: "Bot via @BotFather" },
189
- { value: "whatsapp", label: "WhatsApp", hint: "Via Twilio" },
190
- { value: "email", label: "Email", hint: "IMAP + SMTP" },
272
+ { value: "telegram", label: "Telegram", hint: "Bot via @BotFather — easiest to set up" },
273
+ { value: "whatsapp", label: "WhatsApp", hint: "Via Twilio sandbox" },
274
+ { value: "discord", label: "Discord", hint: "Bot via Discord Developer Portal" },
275
+ { value: "slack", label: "Slack", hint: "Socket Mode bot" },
276
+ { value: "email", label: "Email", hint: "Gmail IMAP/SMTP or any provider" },
277
+ { value: "line", label: "LINE", hint: "LINE Messaging API" },
278
+ { value: "signal", label: "Signal", hint: "Requires signal-cli daemon" },
279
+ { value: "teams", label: "Microsoft Teams", hint: "Azure Bot Framework" },
280
+ { value: "googlechat", label: "Google Chat", hint: "Service account" },
281
+ { value: "matrix", label: "Matrix", hint: "Element / matrix.org" },
282
+ { value: "mattermost", label: "Mattermost", hint: "Self-hosted or cloud" },
283
+ { value: "twitch", label: "Twitch", hint: "Chat commands with !ask prefix" },
284
+ { value: "irc", label: "IRC", hint: "Any IRC network (Libera, Freenode, ...)" },
285
+ { value: "imessage", label: "iMessage", hint: "macOS only — AppleScript polling" },
286
+ { value: "feishu", label: "Feishu / Lark", hint: "Bytedance enterprise messaging" },
287
+ { value: "zalo", label: "Zalo", hint: "Vietnam — 75M+ users" },
288
+ { value: "nextcloud", label: "Nextcloud Talk", hint: "Self-hosted collaboration" },
289
+ { value: "bluebubbles", label: "BlueBubbles", hint: "iMessage relay server on a Mac" },
290
+ { value: "nostr", label: "Nostr", hint: "Decentralized protocol — NIP-04 DMs" },
191
291
  ],
192
292
  required: false,
193
293
  }));
194
294
 
295
+ // ── Per-channel credential collection ─────────────────────────────────────
296
+
195
297
  if (channels.includes("telegram")) {
196
298
  p.note(
197
299
  [
198
- "1. Open Telegram, search for @BotFather",
199
- "2. Send /newbot and follow the prompts",
200
- "3. Copy the bot token it gives you",
300
+ "1. Open Telegram search @BotFather",
301
+ "2. Send /newbot, follow the prompts",
302
+ "3. Copy the bot token (format: 123456789:ABCdef...)",
303
+ "",
304
+ "Optional later — add to .env:",
305
+ " TELEGRAM_ALLOWLIST=123456,987654 (comma-separated chat IDs)",
306
+ " TELEGRAM_MODEL=anthropic:claude-sonnet-4-6",
201
307
  ].join("\n"),
202
- "Get Telegram Token"
308
+ "Telegram Setup"
203
309
  );
204
310
  const token = guard(await p.password({ message: "Telegram bot token" }));
205
311
  if (token) envConfig.TELEGRAM_BOT_TOKEN = token;
@@ -208,39 +314,307 @@ export async function runSetupWizard() {
208
314
  if (channels.includes("whatsapp")) {
209
315
  p.note(
210
316
  [
211
- "1. Go to https://console.twilio.com",
212
- "2. Copy Account SID and Auth Token from dashboard",
213
- "3. Go to Messaging > Try it out > WhatsApp",
214
- "4. Follow sandbox setup instructions",
317
+ "1. Sign up at https://console.twilio.com",
318
+ "2. Copy Account SID + Auth Token from the dashboard",
319
+ "3. Messaging Try it out WhatsApp → join sandbox",
320
+ "4. Sandbox number is pre-filled below (change if you have a dedicated number)",
321
+ "",
322
+ "Optional: WHATSAPP_ALLOWLIST=+1234567890,+9876543210",
215
323
  ].join("\n"),
216
- "Get Twilio Credentials"
324
+ "WhatsApp / Twilio Setup"
217
325
  );
218
- envConfig.TWILIO_ACCOUNT_SID = guard(await p.password({ message: "Twilio Account SID" }));
219
- envConfig.TWILIO_AUTH_TOKEN = guard(await p.password({ message: "Twilio Auth Token" }));
220
- envConfig.TWILIO_WHATSAPP_FROM = guard(await p.text({
221
- message: "Twilio WhatsApp From number",
222
- initialValue: "whatsapp:+14155238886",
223
- }));
326
+ envConfig.TWILIO_ACCOUNT_SID = guard(await p.password({ message: "Twilio Account SID" }));
327
+ envConfig.TWILIO_AUTH_TOKEN = guard(await p.password({ message: "Twilio Auth Token" }));
328
+ envConfig.TWILIO_WHATSAPP_FROM = guard(await p.text({ message: "WhatsApp From number", initialValue: "whatsapp:+14155238886" }));
329
+ }
330
+
331
+ if (channels.includes("discord")) {
332
+ p.note(
333
+ [
334
+ "1. Go to https://discord.com/developers/applications",
335
+ "2. New Application → Bot → Add Bot → Reset Token → copy token",
336
+ "3. Enable 'Message Content Intent' under Privileged Gateway Intents",
337
+ "4. OAuth2 › URL Generator → bot scope → Send Messages, Read Message History",
338
+ "5. Invite bot to your server with the generated URL",
339
+ "",
340
+ "Optional: DISCORD_ALLOWLIST=123456789,987654321 (Discord user snowflake IDs)",
341
+ ].join("\n"),
342
+ "Discord Bot Setup"
343
+ );
344
+ const token = guard(await p.password({ message: "Discord bot token" }));
345
+ if (token) envConfig.DISCORD_BOT_TOKEN = token;
346
+ }
347
+
348
+ if (channels.includes("slack")) {
349
+ p.note(
350
+ [
351
+ "1. Go to https://api.slack.com/apps → Create New App → From scratch",
352
+ "2. Socket Mode → Enable → create App-Level Token (xapp-...) → copy as App Token",
353
+ "3. OAuth & Permissions → Bot Token Scopes: chat:write, im:history, app_mentions:read",
354
+ "4. Install to workspace → copy Bot Token (xoxb-...)",
355
+ "",
356
+ "Optional: SLACK_ALLOWLIST=U01234567,U09876543 (Slack user IDs)",
357
+ ].join("\n"),
358
+ "Slack Setup"
359
+ );
360
+ envConfig.SLACK_BOT_TOKEN = guard(await p.password({ message: "Slack Bot Token (xoxb-...)" }));
361
+ envConfig.SLACK_APP_TOKEN = guard(await p.password({ message: "Slack App Token (xapp-...)" }));
224
362
  }
225
363
 
226
364
  if (channels.includes("email")) {
227
365
  p.note(
228
366
  [
229
- "For Gmail:",
230
- "1. Enable 2-Factor Authentication on your Google account",
231
- "2. Go to https://myaccount.google.com/apppasswords",
232
- "3. Create an app password for \"Mail\"",
233
- "4. Use that 16-char password below (not your Gmail password)",
367
+ "Gmail setup:",
368
+ "1. Google Account › Security › 2-Step Verification enable",
369
+ "2. Google Account › Security › App Passwords → Mail → create",
370
+ "3. Use the 16-char app password below (NOT your Gmail password)",
371
+ "",
372
+ "For other providers: change IMAP/SMTP hosts below.",
373
+ "Optional: EMAIL_ALLOWLIST=alice@example.com,bob@example.com",
234
374
  ].join("\n"),
235
375
  "Email Setup"
236
376
  );
237
- envConfig.EMAIL_USER = guard(await p.text({ message: "Email address" }));
238
- envConfig.EMAIL_PASSWORD = guard(await p.password({ message: "Email app password" }));
377
+ envConfig.EMAIL_USER = guard(await p.text({ message: "Email address" }));
378
+ envConfig.EMAIL_PASSWORD = guard(await p.password({ message: "App password" }));
239
379
  envConfig.EMAIL_IMAP_HOST = guard(await p.text({ message: "IMAP host", initialValue: "imap.gmail.com" }));
240
380
  envConfig.EMAIL_SMTP_HOST = guard(await p.text({ message: "SMTP host", initialValue: "smtp.gmail.com" }));
241
381
  }
242
382
 
243
- const activeChannels = ["HTTP", ...channels.map((c) => c.charAt(0).toUpperCase() + c.slice(1))];
383
+ if (channels.includes("line")) {
384
+ p.note(
385
+ [
386
+ "1. Go to https://developers.line.biz → Create a Provider",
387
+ "2. Create a Messaging API channel",
388
+ "3. Basic settings → Channel Secret",
389
+ "4. Messaging API → Channel Access Token (long-lived) → Issue",
390
+ "5. Set webhook URL to: https://your-server/webhooks/line",
391
+ ].join("\n"),
392
+ "LINE Setup"
393
+ );
394
+ envConfig.LINE_CHANNEL_ACCESS_TOKEN = guard(await p.password({ message: "LINE Channel Access Token" }));
395
+ envConfig.LINE_CHANNEL_SECRET = guard(await p.password({ message: "LINE Channel Secret" }));
396
+ }
397
+
398
+ if (channels.includes("signal")) {
399
+ p.note(
400
+ [
401
+ "Requires signal-cli running as a REST daemon:",
402
+ " npm install -g signal-cli (or download from GitHub)",
403
+ " signal-cli -u +1234567890 register",
404
+ " signal-cli -u +1234567890 verify <code>",
405
+ " signal-cli -u +1234567890 daemon --http 127.0.0.1:8080",
406
+ "",
407
+ "Optional: SIGNAL_ALLOWLIST=+1234567890,+0987654321",
408
+ ].join("\n"),
409
+ "Signal Setup"
410
+ );
411
+ envConfig.SIGNAL_CLI_URL = guard(await p.text({ message: "signal-cli REST URL", initialValue: "http://127.0.0.1:8080" }));
412
+ envConfig.SIGNAL_PHONE_NUMBER = guard(await p.text({ message: "Your Signal phone number (+1234567890)" }));
413
+ }
414
+
415
+ if (channels.includes("teams")) {
416
+ p.note(
417
+ [
418
+ "1. Go to https://portal.azure.com → Create an Azure Bot",
419
+ "2. Configuration → set messaging endpoint: https://your-server/webhooks/teams",
420
+ "3. Copy App ID from Configuration",
421
+ "4. Manage Password → New client secret → copy value",
422
+ "5. Channels → Add Microsoft Teams",
423
+ ].join("\n"),
424
+ "Microsoft Teams Setup"
425
+ );
426
+ envConfig.TEAMS_APP_ID = guard(await p.text({ message: "Teams App ID (UUID)" }));
427
+ envConfig.TEAMS_APP_PASSWORD = guard(await p.password({ message: "Teams App Password (client secret)" }));
428
+ }
429
+
430
+ if (channels.includes("googlechat")) {
431
+ p.note(
432
+ [
433
+ "1. Google Cloud Console → Enable 'Google Chat API'",
434
+ "2. IAM → Service Accounts → Create → download JSON key",
435
+ "3. Chat API → Configuration → Bot URL: https://your-server/webhooks/googlechat",
436
+ "4. Paste the ENTIRE JSON key file contents as one line below",
437
+ ].join("\n"),
438
+ "Google Chat Setup"
439
+ );
440
+ envConfig.GOOGLE_CHAT_SERVICE_ACCOUNT = guard(await p.text({ message: "Service account JSON (one line)" }));
441
+ envConfig.GOOGLE_CHAT_PROJECT_NUMBER = guard(await p.text({ message: "Google Cloud project number" }));
442
+ }
443
+
444
+ if (channels.includes("matrix")) {
445
+ p.note(
446
+ [
447
+ "1. Create a bot account on matrix.org or your homeserver",
448
+ "2. Get access token: POST /_matrix/client/v3/login",
449
+ " Body: {\"type\":\"m.login.password\",\"user\":\"@bot:matrix.org\",\"password\":\"...\"}",
450
+ "3. Copy 'access_token' from the response",
451
+ ].join("\n"),
452
+ "Matrix Setup"
453
+ );
454
+ envConfig.MATRIX_HOMESERVER_URL = guard(await p.text({ message: "Homeserver URL", initialValue: "https://matrix.org" }));
455
+ envConfig.MATRIX_ACCESS_TOKEN = guard(await p.password({ message: "Bot access token" }));
456
+ envConfig.MATRIX_BOT_USER_ID = guard(await p.text({ message: "Bot user ID (e.g. @daemora:matrix.org)" }));
457
+ }
458
+
459
+ if (channels.includes("mattermost")) {
460
+ p.note(
461
+ [
462
+ "1. System Console → Integrations → Bot Accounts → Enable",
463
+ "2. Integrations → Bot Accounts → Add Bot Account",
464
+ "3. Copy the bot token shown after creation",
465
+ "4. Find bot user ID: GET /api/v4/users/me (Authorization: Bearer <token>)",
466
+ ].join("\n"),
467
+ "Mattermost Setup"
468
+ );
469
+ envConfig.MATTERMOST_URL = guard(await p.text({ message: "Mattermost URL", placeholder: "https://your-mattermost.example.com" }));
470
+ envConfig.MATTERMOST_TOKEN = guard(await p.password({ message: "Bot token" }));
471
+ envConfig.MATTERMOST_BOT_USER_ID = guard(await p.text({ message: "Bot user ID (optional)", initialValue: "" }));
472
+ }
473
+
474
+ if (channels.includes("twitch")) {
475
+ p.note(
476
+ [
477
+ "1. Create a Twitch account for your bot",
478
+ "2. Get OAuth token: https://twitchapps.com/tmi/",
479
+ " (authorize with your BOT account, not your main account)",
480
+ "3. Copy the oauth:... token",
481
+ "",
482
+ "Users trigger the bot with: !ask <message>",
483
+ "Change prefix with: TWITCH_COMMAND_PREFIX=!ai",
484
+ ].join("\n"),
485
+ "Twitch Setup"
486
+ );
487
+ envConfig.TWITCH_BOT_USERNAME = guard(await p.text({ message: "Bot Twitch username" }));
488
+ envConfig.TWITCH_OAUTH_TOKEN = guard(await p.password({ message: "OAuth token (oauth:...)" }));
489
+ envConfig.TWITCH_CHANNEL = guard(await p.text({ message: "Channel to join (without #)" }));
490
+ }
491
+
492
+ if (channels.includes("irc")) {
493
+ p.note(
494
+ [
495
+ "Connects to any IRC network. No external packages — uses raw TCP.",
496
+ "Popular networks: irc.libera.chat irc.freenode.net irc.oftc.net",
497
+ "",
498
+ "Optional: IRC_PASSWORD=<nickserv-password>",
499
+ ].join("\n"),
500
+ "IRC Setup"
501
+ );
502
+ envConfig.IRC_SERVER = guard(await p.text({ message: "IRC server", initialValue: "irc.libera.chat" }));
503
+ envConfig.IRC_PORT = guard(await p.text({ message: "IRC port", initialValue: "6667" }));
504
+ envConfig.IRC_NICK = guard(await p.text({ message: "Bot nick", initialValue: "daemora-bot" }));
505
+ envConfig.IRC_CHANNEL = guard(await p.text({ message: "Channel to join (e.g. #mychannel)" }));
506
+ const ircPass = guard(await p.password({ message: "NickServ password (optional, leave blank)" }));
507
+ if (ircPass) envConfig.IRC_PASSWORD = ircPass;
508
+ }
509
+
510
+ if (channels.includes("imessage")) {
511
+ if (process.platform !== "darwin") {
512
+ p.log.warn("iMessage requires macOS. Skipping.");
513
+ } else {
514
+ p.note(
515
+ [
516
+ "Polls iMessages via AppleScript — macOS only.",
517
+ "Requires: Messages app open + Accessibility permissions granted to Terminal",
518
+ "",
519
+ "System Preferences › Privacy & Security › Accessibility → allow Terminal",
520
+ "",
521
+ "Optional: IMESSAGE_ALLOWLIST=+1234567890,user@icloud.com",
522
+ ].join("\n"),
523
+ "iMessage Setup"
524
+ );
525
+ envConfig.IMESSAGE_ENABLED = "true";
526
+ envConfig.IMESSAGE_POLL_INTERVAL_MS = "5000";
527
+ }
528
+ }
529
+
530
+ if (channels.includes("feishu")) {
531
+ p.note(
532
+ [
533
+ "1. Go to https://open.feishu.cn/app → Create App",
534
+ "2. Credentials & Basic Info → copy App ID and App Secret",
535
+ "3. Add capability: Bot",
536
+ "4. Event Subscriptions → set webhook: https://your-server/channels/feishu",
537
+ "5. Add events: im.message.receive_v1",
538
+ ].join("\n"),
539
+ "Feishu / Lark Setup"
540
+ );
541
+ envConfig.FEISHU_APP_ID = guard(await p.text({ message: "Feishu App ID" }));
542
+ envConfig.FEISHU_APP_SECRET = guard(await p.password({ message: "Feishu App Secret" }));
543
+ envConfig.FEISHU_VERIFICATION_TOKEN = guard(await p.password({ message: "Verification token (optional)", }));
544
+ }
545
+
546
+ if (channels.includes("zalo")) {
547
+ p.note(
548
+ [
549
+ "1. Register Official Account at https://oa.zalo.me",
550
+ "2. Create app at https://developers.zalo.me → API Tools",
551
+ "3. Copy App ID and App Secret",
552
+ "4. Get access token via OAuth",
553
+ "5. Set webhook: https://your-server/channels/zalo",
554
+ ].join("\n"),
555
+ "Zalo Setup"
556
+ );
557
+ envConfig.ZALO_APP_ID = guard(await p.text({ message: "Zalo App ID" }));
558
+ envConfig.ZALO_APP_SECRET = guard(await p.password({ message: "Zalo App Secret" }));
559
+ envConfig.ZALO_ACCESS_TOKEN = guard(await p.password({ message: "Zalo Access Token" }));
560
+ }
561
+
562
+ if (channels.includes("nextcloud")) {
563
+ p.note(
564
+ [
565
+ "1. Log into Nextcloud → Profile icon → Settings → Security",
566
+ "2. Devices & Sessions → create App Password for the bot account",
567
+ "3. Find the room token in Talk URL: /call/<room-token>",
568
+ ].join("\n"),
569
+ "Nextcloud Talk Setup"
570
+ );
571
+ envConfig.NEXTCLOUD_URL = guard(await p.text({ message: "Nextcloud URL", placeholder: "https://cloud.example.com" }));
572
+ envConfig.NEXTCLOUD_USER = guard(await p.text({ message: "Bot username", initialValue: "daemora-bot" }));
573
+ envConfig.NEXTCLOUD_PASSWORD = guard(await p.password({ message: "App password" }));
574
+ envConfig.NEXTCLOUD_ROOM_TOKEN = guard(await p.text({ message: "Room token (from Talk URL)" }));
575
+ }
576
+
577
+ if (channels.includes("bluebubbles")) {
578
+ p.note(
579
+ [
580
+ "BlueBubbles is an iMessage relay server that runs on a Mac.",
581
+ "Download: https://bluebubbles.app",
582
+ "",
583
+ "1. Install BlueBubbles on a Mac that is signed into iMessage",
584
+ "2. Settings → Server → copy Server URL and Password",
585
+ "3. The URL is usually http://192.168.x.x:1234 on your LAN",
586
+ ].join("\n"),
587
+ "BlueBubbles Setup"
588
+ );
589
+ envConfig.BLUEBUBBLES_URL = guard(await p.text({ message: "BlueBubbles server URL", placeholder: "http://192.168.1.100:1234" }));
590
+ envConfig.BLUEBUBBLES_PASSWORD = guard(await p.password({ message: "BlueBubbles server password" }));
591
+ }
592
+
593
+ if (channels.includes("nostr")) {
594
+ p.note(
595
+ [
596
+ "Nostr is a decentralized protocol. The bot receives NIP-04 encrypted DMs.",
597
+ "",
598
+ "Generate a private key:",
599
+ " openssl rand -hex 32",
600
+ "",
601
+ "Default relays are pre-filled. Add/remove as needed.",
602
+ "Share your bot's npub (public key) so users can DM it.",
603
+ ].join("\n"),
604
+ "Nostr Setup"
605
+ );
606
+ envConfig.NOSTR_PRIVATE_KEY = guard(await p.password({ message: "Nostr private key (hex, 64 chars)" }));
607
+ const relaysRaw = guard(await p.text({
608
+ message: "Relay URLs (comma-separated)",
609
+ initialValue: "wss://relay.damus.io,wss://nos.lol,wss://relay.nostr.band",
610
+ }));
611
+ envConfig.NOSTR_RELAYS = relaysRaw;
612
+ }
613
+
614
+ const activeChannels = ["HTTP", ...channels.map((c) => {
615
+ const labels = { googlechat: "GoogleChat", imessage: "iMessage", bluebubbles: "BlueBubbles" };
616
+ return labels[c] || c.charAt(0).toUpperCase() + c.slice(1);
617
+ })];
244
618
  p.log.success(`Channels: ${t.bold(activeChannels.join(", "))}`);
245
619
 
246
620
  // ━━━ Step 6: Daemon ━━━
@@ -389,17 +763,17 @@ export async function runSetupWizard() {
389
763
  }));
390
764
 
391
765
  while (addMore) {
392
- p.log.info(`${t.bold("Custom MCP Server")} 3 transport types supported:`);
393
- p.log.info(` ${S.arrow} ${t.accent("stdio")} local subprocess (npx, node, python, go, etc.)`);
394
- p.log.info(` ${S.arrow} ${t.accent("http")} remote HTTP server (streamable MCP)`);
395
- p.log.info(` ${S.arrow} ${t.accent("sse")} remote SSE server (Server-Sent Events)`);
766
+ p.log.info(`${t.bold("Custom MCP Server")} - 3 transport types supported:`);
767
+ p.log.info(` ${S.arrow} ${t.accent("stdio")} - local subprocess (npx, node, python, go, etc.)`);
768
+ p.log.info(` ${S.arrow} ${t.accent("http")} - remote HTTP server (streamable MCP)`);
769
+ p.log.info(` ${S.arrow} ${t.accent("sse")} - remote SSE server (Server-Sent Events)`);
396
770
 
397
771
  const customName = guard(await p.text({
398
772
  message: "Server name (no spaces, e.g. mytools, notion, postgres)",
399
773
  validate: (v) => {
400
774
  if (!v) return "Name is required";
401
775
  if (/\s/.test(v)) return "Name cannot contain spaces";
402
- if (mcpConfig.mcpServers[v]) return `"${v}" already exists choose a different name`;
776
+ if (mcpConfig.mcpServers[v]) return `"${v}" already exists - choose a different name`;
403
777
  },
404
778
  }));
405
779
 
@@ -412,7 +786,7 @@ export async function runSetupWizard() {
412
786
  const transport = guard(await p.select({
413
787
  message: "Transport type",
414
788
  options: [
415
- { value: "stdio", label: "stdio", hint: "Local subprocess npx, node, python, go binary, etc." },
789
+ { value: "stdio", label: "stdio", hint: "Local subprocess - npx, node, python, go binary, etc." },
416
790
  { value: "http", label: "http", hint: "Remote HTTP endpoint (streamable MCP protocol)" },
417
791
  { value: "sse", label: "sse", hint: "Remote SSE endpoint (Server-Sent Events)" },
418
792
  ],
@@ -486,7 +860,7 @@ export async function runSetupWizard() {
486
860
  if (transport === "sse") serverCfg.transport = "sse";
487
861
 
488
862
  // HTTP/SSE auth: credentials go as HTTP request HEADERS (Authorization, X-API-Key, etc.)
489
- // They are NOT env vars they are sent with every HTTP request to the server.
863
+ // They are NOT env vars - they are sent with every HTTP request to the server.
490
864
  p.note(
491
865
  [
492
866
  "HTTP/SSE servers authenticate via request headers, not env vars.",
@@ -505,7 +879,7 @@ export async function runSetupWizard() {
505
879
  const authType = guard(await p.select({
506
880
  message: "Authentication type",
507
881
  options: [
508
- { value: "none", label: "None", hint: "No auth public or local server" },
882
+ { value: "none", label: "None", hint: "No auth - public or local server" },
509
883
  { value: "bearer", label: "Bearer token", hint: "Authorization: Bearer <token>" },
510
884
  { value: "apikey", label: "API key header", hint: "X-API-Key or custom header name" },
511
885
  { value: "custom", label: "Custom headers", hint: "Add any headers manually" },
@@ -520,7 +894,7 @@ export async function runSetupWizard() {
520
894
  message: "Bearer token (or ${MY_ENV_VAR} to reference an env var)",
521
895
  validate: (v) => !v ? "Required" : undefined,
522
896
  }));
523
- // Store the raw value ${VAR} gets expanded at connect time by MCPClient
897
+ // Store the raw value - ${VAR} gets expanded at connect time by MCPClient
524
898
  serverCfg.headers["Authorization"] = `Bearer ${token}`;
525
899
 
526
900
  } else if (authType === "apikey") {
@@ -536,7 +910,7 @@ export async function runSetupWizard() {
536
910
  serverCfg.headers[headerName] = apiKey;
537
911
 
538
912
  } else {
539
- // custom loop
913
+ // custom - loop
540
914
  let addHeader = true;
541
915
  while (addHeader) {
542
916
  const headerName = guard(await p.text({
@@ -650,6 +1024,7 @@ export async function runSetupWizard() {
650
1024
 
651
1025
  const secretKeys = [
652
1026
  "OPENAI_API_KEY", "ANTHROPIC_API_KEY", "GOOGLE_AI_API_KEY",
1027
+ "XAI_API_KEY", "DEEPSEEK_API_KEY", "MISTRAL_API_KEY",
653
1028
  "TELEGRAM_BOT_TOKEN", "TWILIO_ACCOUNT_SID", "TWILIO_AUTH_TOKEN",
654
1029
  "EMAIL_PASSWORD",
655
1030
  ];
@@ -684,7 +1059,7 @@ export async function runSetupWizard() {
684
1059
  ];
685
1060
 
686
1061
  const categories = {
687
- "AI Model": ["OPENAI_API_KEY", "ANTHROPIC_API_KEY", "GOOGLE_AI_API_KEY", "DEFAULT_MODEL"],
1062
+ "AI Model": ["OPENAI_API_KEY", "ANTHROPIC_API_KEY", "GOOGLE_AI_API_KEY", "XAI_API_KEY", "DEEPSEEK_API_KEY", "MISTRAL_API_KEY", "DEFAULT_MODEL"],
688
1063
  "Server": ["PORT"],
689
1064
  "Safety": ["PERMISSION_TIER", "MAX_COST_PER_TASK", "MAX_DAILY_COST"],
690
1065
  "Filesystem": ["ALLOWED_PATHS", "BLOCKED_PATHS", "RESTRICT_COMMANDS"],
@@ -722,7 +1097,7 @@ export async function runSetupWizard() {
722
1097
  const dm = new DaemonManager();
723
1098
  dm.install();
724
1099
  } catch {
725
- // Non-fatal user can install later
1100
+ // Non-fatal - user can install later
726
1101
  }
727
1102
  }
728
1103