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
package/src/cli.js CHANGED
@@ -17,36 +17,51 @@ import daemonManager from "./daemon/DaemonManager.js";
17
17
  import secretVault from "./safety/SecretVault.js";
18
18
  import { readFileSync, writeFileSync, existsSync } from "fs";
19
19
  import { join } from "path";
20
+ import { execSync } from "child_process";
20
21
 
22
+ // ── Color palette — matches Daemora UI exactly ──────────────────────────────
21
23
  const P = {
22
- brand: "#7C6AFF",
23
- accent: "#4ECDC4",
24
- success: "#2ECC71",
25
- error: "#E74C3C",
26
- muted: "#7F8C8D",
27
- dim: "#555E68",
24
+ cyan: "#00d9ff", // primary brand (cyan)
25
+ teal: "#4ECDC4", // secondary accent (teal)
26
+ red: "#ff4458", // danger / features (logo horns color)
27
+ green: "#00ff88", // success / security
28
+ amber: "#ffaa00", // warning / [NEW] badges
29
+ muted: "#64748b", // slate-500
30
+ dim: "#94a3b8", // slate-400
31
+ border: "#1f1f2e", // border color
32
+ // semantic aliases
33
+ get brand() { return this.cyan; },
34
+ get accent() { return this.teal; },
35
+ get success() { return this.green; },
36
+ get warning() { return this.amber; },
37
+ get error() { return this.red; },
28
38
  };
29
39
 
30
40
  const t = {
31
- brand: (s) => chalk.hex(P.brand)(s),
32
- accent: (s) => chalk.hex(P.accent)(s),
33
- success: (s) => chalk.hex(P.success)(s),
34
- error: (s) => chalk.hex(P.error)(s),
35
- muted: (s) => chalk.hex(P.muted)(s),
36
- bold: (s) => chalk.bold(s),
37
- h: (s) => chalk.bold.hex(P.brand)(s),
38
- cmd: (s) => chalk.hex(P.accent)(s),
39
- dim: (s) => chalk.hex(P.dim)(s),
41
+ brand: (s) => chalk.hex(P.cyan)(s),
42
+ accent: (s) => chalk.hex(P.teal)(s),
43
+ success: (s) => chalk.hex(P.green)(s),
44
+ warning: (s) => chalk.hex(P.amber)(s),
45
+ error: (s) => chalk.hex(P.red)(s),
46
+ muted: (s) => chalk.hex(P.muted)(s),
47
+ dim: (s) => chalk.hex(P.dim)(s),
48
+ bold: (s) => chalk.bold(s),
49
+ h: (s) => chalk.bold.hex(P.cyan)(s),
50
+ h2: (s) => chalk.bold.hex(P.teal)(s),
51
+ cmd: (s) => chalk.hex(P.teal)(s),
52
+ new: (s) => chalk.hex(P.amber)(s),
40
53
  };
41
54
 
42
55
  const S = {
43
- check: chalk.hex(P.success)("\u2714"),
44
- cross: chalk.hex(P.error)("\u2718"),
45
- arrow: chalk.hex(P.brand)("\u25B8"),
46
- dot: chalk.hex(P.muted)("\u00B7"),
47
- bar: chalk.hex(P.dim)("\u2502"),
48
- info: chalk.hex(P.accent)("\u25C6"),
49
- lock: chalk.hex("#F1C40F")("\u25A3"),
56
+ check: chalk.hex(P.green)("\u2714"),
57
+ cross: chalk.hex(P.red)("\u2718"),
58
+ arrow: chalk.hex(P.cyan)("\u25B8"),
59
+ dot: chalk.hex(P.muted)("\u00B7"),
60
+ bar: chalk.hex(P.muted)("\u2502"),
61
+ info: chalk.hex(P.teal)("\u25C6"),
62
+ lock: chalk.hex(P.amber)("\u25A3"),
63
+ eye: chalk.hex(P.red)("\u25C9"),
64
+ star: chalk.hex(P.amber)("\u2605"),
50
65
  };
51
66
 
52
67
  const [,, command, subcommand, ...rest] = process.argv;
@@ -103,6 +118,18 @@ async function main() {
103
118
  await handleDoctor();
104
119
  break;
105
120
 
121
+ case "channels":
122
+ await handleChannels(subcommand);
123
+ break;
124
+
125
+ case "models":
126
+ await handleModels();
127
+ break;
128
+
129
+ case "tools":
130
+ await handleTools(subcommand);
131
+ break;
132
+
106
133
  case "setup":
107
134
  const { runSetupWizard } = await import("./setup/wizard.js");
108
135
  await runSetupWizard();
@@ -174,7 +201,6 @@ function handleDaemon(action) {
174
201
  }
175
202
 
176
203
  case "logs": {
177
- const { execSync } = await import("child_process");
178
204
  const stdoutLog = join(daemonManager.logsDir, "daemon-stdout.log");
179
205
  const stderrLog = join(daemonManager.logsDir, "daemon-stderr.log");
180
206
  const lines = process.argv[4] || "50";
@@ -368,7 +394,7 @@ async function handleMCP(action, args) {
368
394
  const transport = pGuard(await pi.select({
369
395
  message: "Transport type",
370
396
  options: [
371
- { value: "stdio", label: "stdio", hint: "Local subprocess npx, node, python, binary" },
397
+ { value: "stdio", label: "stdio", hint: "Local subprocess - npx, node, python, binary" },
372
398
  { value: "http", label: "http", hint: "Remote HTTP (streamable MCP)" },
373
399
  { value: "sse", label: "sse", hint: "Remote SSE (Server-Sent Events)" },
374
400
  ],
@@ -432,7 +458,7 @@ async function handleMCP(action, args) {
432
458
  { value: "none", label: "None", hint: "No auth needed" },
433
459
  { value: "bearer", label: "Bearer token", hint: "Authorization: Bearer <token>" },
434
460
  { value: "apikey", label: "API key header", hint: "X-API-Key: <key> or custom header name" },
435
- { value: "custom", label: "Custom headers", hint: "Any headers you name them" },
461
+ { value: "custom", label: "Custom headers", hint: "Any headers - you name them" },
436
462
  ],
437
463
  }));
438
464
 
@@ -460,7 +486,7 @@ async function handleMCP(action, args) {
460
486
  serverConfig.headers[headerName] = apiKey;
461
487
 
462
488
  } else {
463
- // custom loop
489
+ // custom - loop
464
490
  let more = true;
465
491
  while (more) {
466
492
  const headerName = pGuard(await pi.text({
@@ -577,7 +603,7 @@ async function handleMCP(action, args) {
577
603
  }
578
604
 
579
605
  case "reload": {
580
- // daemora mcp reload <name> tells the live agent to reconnect, or just validates config
606
+ // daemora mcp reload <name> - tells the live agent to reconnect, or just validates config
581
607
  const [name] = args;
582
608
  if (!name) {
583
609
  console.error(`\n ${S.cross} Usage: daemora mcp reload ${t.dim("<name>")}\n`);
@@ -698,7 +724,7 @@ function handleSandbox(action, args) {
698
724
  const updated = [...new Set([...allowedPaths, newPath])];
699
725
  writeEnvKey("ALLOWED_PATHS", updated.join(","));
700
726
  console.log(`\n${header} ${S.check} ${t.bold(newPath)} added to allowed paths.`);
701
- console.log(` ${S.arrow} Scoped mode active agent can only access: ${t.bold(updated.join(", "))}\n`);
727
+ console.log(` ${S.arrow} Scoped mode active - agent can only access: ${t.bold(updated.join(", "))}\n`);
702
728
  break;
703
729
  }
704
730
 
@@ -715,7 +741,7 @@ function handleSandbox(action, args) {
715
741
  }
716
742
  if (updated.length === 0) {
717
743
  deleteEnvKey("ALLOWED_PATHS");
718
- console.log(`\n${header} ${S.check} ${t.bold(rmPath)} removed. No allowed paths left switching to global mode.\n`);
744
+ console.log(`\n${header} ${S.check} ${t.bold(rmPath)} removed. No allowed paths left - switching to global mode.\n`);
719
745
  } else {
720
746
  writeEnvKey("ALLOWED_PATHS", updated.join(","));
721
747
  console.log(`\n${header} ${S.check} ${t.bold(rmPath)} removed. Remaining: ${t.bold(updated.join(", "))}\n`);
@@ -773,7 +799,7 @@ function handleSandbox(action, args) {
773
799
  deleteEnvKey("ALLOWED_PATHS");
774
800
  deleteEnvKey("BLOCKED_PATHS");
775
801
  deleteEnvKey("RESTRICT_COMMANDS");
776
- console.log(`\n${header} ${S.check} Filesystem scoping cleared global mode restored.`);
802
+ console.log(`\n${header} ${S.check} Filesystem scoping cleared - global mode restored.`);
777
803
  console.log(` ${t.muted("Agent can now access any file the OS allows (hardcoded security patterns still active).")}\n`);
778
804
  break;
779
805
  }
@@ -1036,9 +1062,78 @@ async function handleTenant(action, args) {
1036
1062
  break;
1037
1063
  }
1038
1064
 
1065
+ case "channel": {
1066
+ // daemora tenant channel set <tenantId> <key> <value>
1067
+ // daemora tenant channel unset <tenantId> <key>
1068
+ // daemora tenant channel list <tenantId>
1069
+ const VALID_KEYS = ["email", "email_password", "resend_api_key", "resend_from", "twilio_account_sid", "twilio_auth_token", "twilio_phone_from"];
1070
+ const [channelAction, ...channelArgs] = args;
1071
+ if (!channelAction) {
1072
+ console.error(`\n ${S.cross} Usage: daemora tenant channel ${t.dim("[set|unset|list]")} ${t.dim("<tenantId> ...")}\n`);
1073
+ process.exit(1);
1074
+ }
1075
+ const { default: tm } = await import("./tenants/TenantManager.js");
1076
+ switch (channelAction) {
1077
+ case "set": {
1078
+ const [tenantId, key, value] = channelArgs;
1079
+ if (!tenantId || !key || !value) {
1080
+ console.error(`\n ${S.cross} Usage: daemora tenant channel set ${t.dim("<tenantId> <key> <value>")}\n`);
1081
+ console.log(` ${t.muted("Valid keys:")} ${VALID_KEYS.join(", ")}\n`);
1082
+ process.exit(1);
1083
+ }
1084
+ if (!VALID_KEYS.includes(key)) {
1085
+ console.error(`\n ${S.cross} Invalid key: "${key}"`);
1086
+ console.log(` ${t.muted("Valid keys:")} ${VALID_KEYS.join(", ")}\n`);
1087
+ process.exit(1);
1088
+ }
1089
+ tm.setChannelConfig(tenantId, key, value);
1090
+ console.log(`\n ${S.check} ${t.success("Channel config set")} ${t.muted(`${tenantId} → ${key} = [encrypted]`)}\n`);
1091
+ break;
1092
+ }
1093
+ case "unset": {
1094
+ const [tenantId, key] = channelArgs;
1095
+ if (!tenantId || !key) {
1096
+ console.error(`\n ${S.cross} Usage: daemora tenant channel unset ${t.dim("<tenantId> <key>")}\n`);
1097
+ process.exit(1);
1098
+ }
1099
+ const deleted = tm.deleteChannelConfig(tenantId, key);
1100
+ if (deleted) {
1101
+ console.log(`\n ${S.check} ${t.success("Channel config removed")} ${t.muted(`${tenantId} → ${key}`)}\n`);
1102
+ } else {
1103
+ console.log(`\n ${S.cross} Key "${key}" not found for ${tenantId}\n`);
1104
+ }
1105
+ break;
1106
+ }
1107
+ case "list": {
1108
+ const [tenantId] = channelArgs;
1109
+ if (!tenantId) {
1110
+ console.error(`\n ${S.cross} Usage: daemora tenant channel list ${t.dim("<tenantId>")}\n`);
1111
+ process.exit(1);
1112
+ }
1113
+ const keys = tm.listChannelConfigKeys(tenantId);
1114
+ console.log(`\n ${t.h("Channel config")} ${t.muted(tenantId)}\n`);
1115
+ if (keys.length === 0) {
1116
+ console.log(` ${t.muted("No channel credentials stored.")}`);
1117
+ console.log(` ${t.dim("Use: daemora tenant channel set <id> <key> <value>")}\n`);
1118
+ } else {
1119
+ for (const k of keys) {
1120
+ console.log(` ${S.check} ${t.accent(k)} ${t.muted("[encrypted]")}`);
1121
+ }
1122
+ console.log();
1123
+ }
1124
+ break;
1125
+ }
1126
+ default:
1127
+ console.error(`\n ${S.cross} Unknown channel command: ${channelAction}`);
1128
+ console.log(` ${t.muted("Usage:")} daemora tenant channel ${t.dim("[set|unset|list]")}\n`);
1129
+ process.exit(1);
1130
+ }
1131
+ break;
1132
+ }
1133
+
1039
1134
  default:
1040
1135
  console.error(`\n ${S.cross} Unknown tenant command: ${action || "(none)"}`);
1041
- console.log(` ${t.muted("Usage:")} daemora tenant ${t.dim("[list|show|set|plan|suspend|unsuspend|reset|delete|apikey]")}\n`);
1136
+ console.log(` ${t.muted("Usage:")} daemora tenant ${t.dim("[list|show|set|plan|suspend|unsuspend|reset|delete|apikey|channel]")}\n`);
1042
1137
  process.exit(1);
1043
1138
  }
1044
1139
  }
@@ -1089,7 +1184,7 @@ async function handleDoctor() {
1089
1184
  }
1090
1185
 
1091
1186
  // 3. HTTP channel disabled (no unauthenticated /chat endpoint)
1092
- // The HTTP chat endpoint is permanently disabled in Daemora always passes
1187
+ // The HTTP chat endpoint is permanently disabled in Daemora - always passes
1093
1188
  pass("HTTP /chat endpoint disabled");
1094
1189
 
1095
1190
  // 4. All enabled channels have allowlists
@@ -1176,16 +1271,700 @@ async function handleDoctor() {
1176
1271
  const issues = checks.filter(c => c.score === 0);
1177
1272
  const critical = issues.filter(c => c.icon === t.error("✘")).length;
1178
1273
  if (critical > 0) {
1179
- console.log(` ${t.error(`${critical} critical issue(s) fix immediately.`)}`);
1274
+ console.log(` ${t.error(`${critical} critical issue(s) - fix immediately.`)}`);
1180
1275
  }
1181
1276
  const warnings = issues.filter(c => c.icon !== t.error("✘")).length;
1182
1277
  if (warnings > 0) {
1183
- console.log(` ${chalk.hex("#F1C40F")(`${warnings} warning(s) recommended fixes.`)}`);
1278
+ console.log(` ${chalk.hex("#F1C40F")(`${warnings} warning(s) - recommended fixes.`)}`);
1184
1279
  }
1185
1280
  console.log("");
1186
1281
  }
1187
1282
  }
1188
1283
 
1284
+ // ─── Channels ─────────────────────────────────────────────────────────────────
1285
+
1286
+ const CHANNEL_DEFS = [
1287
+ {
1288
+ name: "telegram", label: "Telegram", desc: "Bot via @BotFather",
1289
+ envRequired: ["TELEGRAM_BOT_TOKEN"],
1290
+ envOptional: [
1291
+ ["TELEGRAM_ALLOWLIST", "Comma-separated chat IDs allowed to message the bot. Empty = open."],
1292
+ ["TELEGRAM_MODEL", "Model override for this channel (e.g. anthropic:claude-sonnet-4-6)"],
1293
+ ],
1294
+ setup: [
1295
+ "1. Open Telegram → search @BotFather",
1296
+ "2. Send /newbot and follow the prompts",
1297
+ "3. Copy the token (format: 123456789:ABCdef...)",
1298
+ ],
1299
+ tenantKey: "telegram",
1300
+ },
1301
+ {
1302
+ name: "whatsapp", label: "WhatsApp", desc: "Via Twilio (sandbox or dedicated number)",
1303
+ envRequired: ["TWILIO_ACCOUNT_SID", "TWILIO_AUTH_TOKEN"],
1304
+ envOptional: [
1305
+ ["TWILIO_WHATSAPP_FROM", "Sending number (default: whatsapp:+14155238886 sandbox)"],
1306
+ ["WHATSAPP_ALLOWLIST", "Comma-separated phone numbers allowed (+1234567890)"],
1307
+ ["WHATSAPP_MODEL", "Model override for this channel"],
1308
+ ],
1309
+ setup: [
1310
+ "1. Sign up at https://console.twilio.com",
1311
+ "2. Copy Account SID + Auth Token from the dashboard",
1312
+ "3. Messaging › Try it out › WhatsApp → join sandbox",
1313
+ ],
1314
+ tenantKey: "whatsapp",
1315
+ },
1316
+ {
1317
+ name: "discord", label: "Discord", desc: "Bot via Discord Developer Portal",
1318
+ envRequired: ["DISCORD_BOT_TOKEN"],
1319
+ envOptional: [
1320
+ ["DISCORD_ALLOWLIST", "Comma-separated Discord user snowflake IDs"],
1321
+ ["DISCORD_MODEL", "Model override for this channel"],
1322
+ ],
1323
+ setup: [
1324
+ "1. https://discord.com/developers/applications → New Application → Bot",
1325
+ "2. Reset Token → copy it",
1326
+ "3. Enable 'Message Content Intent' under Privileged Intents",
1327
+ "4. OAuth2 URL Generator → bot scope → Send Messages, Read Message History",
1328
+ "5. Invite bot to your server with the generated URL",
1329
+ ],
1330
+ tenantKey: "discord",
1331
+ },
1332
+ {
1333
+ name: "slack", label: "Slack", desc: "Socket Mode bot",
1334
+ envRequired: ["SLACK_BOT_TOKEN", "SLACK_APP_TOKEN"],
1335
+ envOptional: [
1336
+ ["SLACK_ALLOWLIST", "Comma-separated Slack user IDs (Uxxxxxxxxx)"],
1337
+ ["SLACK_MODEL", "Model override for this channel"],
1338
+ ],
1339
+ setup: [
1340
+ "1. https://api.slack.com/apps → Create New App → From scratch",
1341
+ "2. Socket Mode → Enable → App-Level Token (xapp-...) → copy as SLACK_APP_TOKEN",
1342
+ "3. OAuth & Permissions → Bot Scopes: chat:write, im:history, app_mentions:read",
1343
+ "4. Install to workspace → Bot Token (xoxb-...) → copy as SLACK_BOT_TOKEN",
1344
+ ],
1345
+ tenantKey: "slack",
1346
+ },
1347
+ {
1348
+ name: "email", label: "Email", desc: "Gmail IMAP/SMTP (reads + sends)",
1349
+ envRequired: ["EMAIL_USER", "EMAIL_PASSWORD"],
1350
+ envOptional: [
1351
+ ["EMAIL_IMAP_HOST", "IMAP host (default: imap.gmail.com)"],
1352
+ ["EMAIL_SMTP_HOST", "SMTP host (default: smtp.gmail.com)"],
1353
+ ["EMAIL_ALLOWLIST", "Comma-separated allowed sender emails"],
1354
+ ["EMAIL_MODEL", "Model override for this channel"],
1355
+ ["RESEND_API_KEY", "Alternative: Resend.com API key for sending only"],
1356
+ ["RESEND_FROM", "Resend from address (e.g. you@yourdomain.com)"],
1357
+ ],
1358
+ setup: [
1359
+ "Gmail: Google Account › Security › 2-Step Verification → enable",
1360
+ "Then: Security › App Passwords → Mail → create 16-char password",
1361
+ "Use that app password as EMAIL_PASSWORD (NOT your Gmail password)",
1362
+ ],
1363
+ tenantKey: "email",
1364
+ },
1365
+ {
1366
+ name: "line", label: "LINE", desc: "LINE Messaging API",
1367
+ envRequired: ["LINE_CHANNEL_ACCESS_TOKEN", "LINE_CHANNEL_SECRET"],
1368
+ envOptional: [
1369
+ ["LINE_ALLOWLIST", "Comma-separated LINE user IDs (Uxxxxxxxxxx)"],
1370
+ ["LINE_MODEL", "Model override for this channel"],
1371
+ ],
1372
+ setup: [
1373
+ "1. https://developers.line.biz → Create Provider → Messaging API channel",
1374
+ "2. Basic settings → Channel Secret",
1375
+ "3. Messaging API → Channel Access Token (long-lived) → Issue",
1376
+ "4. Webhook URL: https://your-server/webhooks/line",
1377
+ ],
1378
+ tenantKey: "line",
1379
+ },
1380
+ {
1381
+ name: "signal", label: "Signal", desc: "signal-cli REST daemon",
1382
+ envRequired: ["SIGNAL_CLI_URL", "SIGNAL_PHONE_NUMBER"],
1383
+ envOptional: [
1384
+ ["SIGNAL_ALLOWLIST", "Comma-separated phone numbers (+1234567890)"],
1385
+ ["SIGNAL_MODEL", "Model override for this channel"],
1386
+ ],
1387
+ setup: [
1388
+ "Install signal-cli: https://github.com/AsamK/signal-cli",
1389
+ "Register: signal-cli -u +1234567890 register",
1390
+ "Verify: signal-cli -u +1234567890 verify <code>",
1391
+ "Daemon: signal-cli -u +1234567890 daemon --http 127.0.0.1:8080",
1392
+ ],
1393
+ tenantKey: "signal",
1394
+ },
1395
+ {
1396
+ name: "teams", label: "Microsoft Teams", desc: "Azure Bot Framework",
1397
+ envRequired: ["TEAMS_APP_ID", "TEAMS_APP_PASSWORD"],
1398
+ envOptional: [
1399
+ ["TEAMS_ALLOWLIST", "Comma-separated Teams user IDs or AAD object IDs"],
1400
+ ["TEAMS_MODEL", "Model override for this channel"],
1401
+ ],
1402
+ setup: [
1403
+ "1. https://portal.azure.com → Create an Azure Bot",
1404
+ "2. Configuration → Messaging endpoint: https://your-server/webhooks/teams",
1405
+ "3. Copy App ID + Manage Password → New client secret",
1406
+ "4. Channels → Add Microsoft Teams",
1407
+ ],
1408
+ tenantKey: "teams",
1409
+ },
1410
+ {
1411
+ name: "googlechat", label: "Google Chat", desc: "Google Cloud service account",
1412
+ envRequired: ["GOOGLE_CHAT_SERVICE_ACCOUNT"],
1413
+ envOptional: [
1414
+ ["GOOGLE_CHAT_PROJECT_NUMBER", "GCP project number"],
1415
+ ["GOOGLE_CHAT_ALLOWLIST", "Comma-separated Google user IDs or emails"],
1416
+ ["GOOGLE_CHAT_MODEL", "Model override for this channel"],
1417
+ ],
1418
+ setup: [
1419
+ "1. GCP Console → Enable 'Google Chat API'",
1420
+ "2. IAM → Service Accounts → Create → download JSON key",
1421
+ "3. Chat API → Configuration → Bot URL: https://your-server/webhooks/googlechat",
1422
+ "4. Paste entire JSON key as one line into GOOGLE_CHAT_SERVICE_ACCOUNT",
1423
+ ],
1424
+ tenantKey: "googlechat",
1425
+ },
1426
+ {
1427
+ name: "matrix", label: "Matrix", desc: "Element / matrix.org protocol",
1428
+ envRequired: ["MATRIX_HOMESERVER_URL", "MATRIX_ACCESS_TOKEN"],
1429
+ envOptional: [
1430
+ ["MATRIX_BOT_USER_ID", "Bot user ID (e.g. @daemora:matrix.org)"],
1431
+ ],
1432
+ setup: [
1433
+ "1. Create bot account on matrix.org or your homeserver",
1434
+ "2. Get access token:",
1435
+ " POST /_matrix/client/v3/login",
1436
+ " {\"type\":\"m.login.password\",\"user\":\"@bot:matrix.org\",\"password\":\"...\"}",
1437
+ "3. Copy 'access_token' from response",
1438
+ ],
1439
+ tenantKey: "matrix",
1440
+ },
1441
+ {
1442
+ name: "mattermost", label: "Mattermost", desc: "WebSocket bot",
1443
+ envRequired: ["MATTERMOST_URL", "MATTERMOST_TOKEN"],
1444
+ envOptional: [
1445
+ ["MATTERMOST_BOT_USER_ID", "Bot user ID"],
1446
+ ["MATTERMOST_BOT_USERNAME", "Bot username (default: daemora-bot)"],
1447
+ ],
1448
+ setup: [
1449
+ "1. System Console → Integrations → Bot Accounts → Enable",
1450
+ "2. Integrations → Bot Accounts → Add Bot Account",
1451
+ "3. Copy the bot token shown after creation",
1452
+ "4. Find bot user ID: GET /api/v4/users/me (Authorization: Bearer <token>)",
1453
+ ],
1454
+ tenantKey: "mattermost",
1455
+ },
1456
+ {
1457
+ name: "twitch", label: "Twitch", desc: "Chat commands (!ask prefix)",
1458
+ envRequired: ["TWITCH_BOT_USERNAME", "TWITCH_OAUTH_TOKEN", "TWITCH_CHANNEL"],
1459
+ envOptional: [
1460
+ ["TWITCH_COMMAND_PREFIX", "Command prefix (default: !ask)"],
1461
+ ],
1462
+ setup: [
1463
+ "1. Create a Twitch account for your bot",
1464
+ "2. Get OAuth token at https://twitchapps.com/tmi/ (authorize as bot account)",
1465
+ "3. Copy the oauth:... token",
1466
+ ],
1467
+ tenantKey: "twitch",
1468
+ },
1469
+ {
1470
+ name: "irc", label: "IRC", desc: "Any IRC network — no external packages",
1471
+ envRequired: ["IRC_SERVER", "IRC_NICK"],
1472
+ envOptional: [
1473
+ ["IRC_PORT", "Port (default: 6667)"],
1474
+ ["IRC_CHANNEL", "Channel to join (e.g. #mychannel)"],
1475
+ ["IRC_PASSWORD", "NickServ password"],
1476
+ ],
1477
+ setup: [
1478
+ "Popular networks: irc.libera.chat irc.freenode.net irc.oftc.net",
1479
+ "Uses raw TCP — no npm packages needed.",
1480
+ ],
1481
+ tenantKey: "irc",
1482
+ },
1483
+ {
1484
+ name: "imessage", label: "iMessage", desc: "macOS only — AppleScript polling",
1485
+ envRequired: ["IMESSAGE_ENABLED=true"],
1486
+ envOptional: [
1487
+ ["IMESSAGE_POLL_INTERVAL_MS", "Poll interval in ms (default: 5000)"],
1488
+ ["IMESSAGE_ALLOWLIST", "Comma-separated phone numbers or iCloud emails"],
1489
+ ],
1490
+ setup: [
1491
+ "macOS only. Messages app must be open and signed in.",
1492
+ "System Preferences › Privacy & Security › Accessibility → allow Terminal",
1493
+ "Set IMESSAGE_ENABLED=true in your .env",
1494
+ ],
1495
+ tenantKey: "imessage",
1496
+ },
1497
+ {
1498
+ name: "feishu", label: "Feishu / Lark", desc: "Bytedance enterprise messaging",
1499
+ envRequired: ["FEISHU_APP_ID", "FEISHU_APP_SECRET"],
1500
+ envOptional: [
1501
+ ["FEISHU_VERIFICATION_TOKEN", "Webhook verification token"],
1502
+ ["FEISHU_PORT", "Webhook port (default: 3004)"],
1503
+ ],
1504
+ setup: [
1505
+ "1. https://open.feishu.cn/app → Create App",
1506
+ "2. Credentials & Basic Info → App ID + App Secret",
1507
+ "3. Add capability: Bot",
1508
+ "4. Event Subscriptions → webhook: https://your-server/channels/feishu",
1509
+ "5. Subscribe to: im.message.receive_v1",
1510
+ ],
1511
+ tenantKey: "feishu",
1512
+ },
1513
+ {
1514
+ name: "zalo", label: "Zalo", desc: "Vietnam — 75M+ users",
1515
+ envRequired: ["ZALO_APP_ID", "ZALO_ACCESS_TOKEN"],
1516
+ envOptional: [
1517
+ ["ZALO_APP_SECRET", "Zalo App Secret"],
1518
+ ["ZALO_PORT", "Webhook port (default: 3005)"],
1519
+ ],
1520
+ setup: [
1521
+ "1. Register Official Account at https://oa.zalo.me",
1522
+ "2. Create app at https://developers.zalo.me → API Tools",
1523
+ "3. Get access token via OAuth",
1524
+ "4. Webhook: https://your-server/channels/zalo",
1525
+ ],
1526
+ tenantKey: "zalo",
1527
+ },
1528
+ {
1529
+ name: "nextcloud", label: "Nextcloud Talk", desc: "Self-hosted collaboration",
1530
+ envRequired: ["NEXTCLOUD_URL", "NEXTCLOUD_USER", "NEXTCLOUD_PASSWORD"],
1531
+ envOptional: [
1532
+ ["NEXTCLOUD_ROOM_TOKEN", "Talk room token (from /call/<token> in URL)"],
1533
+ ],
1534
+ setup: [
1535
+ "1. Nextcloud → Profile → Settings → Security",
1536
+ "2. Devices & Sessions → create App Password for the bot account",
1537
+ "3. Find room token in Talk URL: /call/<room-token>",
1538
+ ],
1539
+ tenantKey: "nextcloud",
1540
+ },
1541
+ {
1542
+ name: "bluebubbles", label: "BlueBubbles", desc: "iMessage relay server (Mac required)",
1543
+ envRequired: ["BLUEBUBBLES_URL", "BLUEBUBBLES_PASSWORD"],
1544
+ envOptional: [],
1545
+ setup: [
1546
+ "1. Install BlueBubbles on a Mac signed into iMessage",
1547
+ " https://bluebubbles.app",
1548
+ "2. Settings → Server → copy Server URL + Password",
1549
+ ],
1550
+ tenantKey: "bluebubbles",
1551
+ },
1552
+ {
1553
+ name: "nostr", label: "Nostr", desc: "Decentralized protocol — NIP-04 encrypted DMs",
1554
+ envRequired: ["NOSTR_PRIVATE_KEY"],
1555
+ envOptional: [
1556
+ ["NOSTR_RELAYS", "Comma-separated relay WSS URLs"],
1557
+ ],
1558
+ setup: [
1559
+ "Generate private key: openssl rand -hex 32",
1560
+ "Share the bot's npub (public key) so users can DM it.",
1561
+ "Default relays: relay.damus.io, nos.lol, relay.nostr.band",
1562
+ ],
1563
+ tenantKey: "nostr",
1564
+ },
1565
+ ];
1566
+
1567
+ async function handleChannels() {
1568
+ const { select, isCancel } = await import("@clack/prompts");
1569
+ const w = 67;
1570
+ const line = chalk.hex(P.cyan)("━".repeat(w));
1571
+ const rowLine = chalk.hex(P.border)("─".repeat(w));
1572
+
1573
+ const configured = CHANNEL_DEFS.filter(c => {
1574
+ const key = c.envRequired[0].split("=")[0];
1575
+ return !!process.env[key] || c.envRequired[0].includes("=true") && process.env[key] === "true";
1576
+ });
1577
+
1578
+ // ── Header ────────────────────────────────────────────────────────────────
1579
+ console.log(`\n${line}`);
1580
+ console.log(` ${chalk.bold.hex(P.cyan)("Daemora Channels")} ${chalk.hex(P.muted)(CHANNEL_DEFS.length + " supported · " + configured.length + " configured")}`);
1581
+ console.log(rowLine);
1582
+
1583
+ while (true) {
1584
+ console.log();
1585
+ const options = CHANNEL_DEFS.map(ch => {
1586
+ const isConfigured = ch.envRequired.every(e => {
1587
+ const [k, v] = e.split("=");
1588
+ return v ? process.env[k] === v : !!process.env[k];
1589
+ });
1590
+ const badge = isConfigured ? chalk.hex(P.green)("✔") : chalk.hex(P.border)("○");
1591
+ return {
1592
+ value: ch.name,
1593
+ label: `${badge} ${(isConfigured ? chalk.bold.hex(P.teal) : chalk.hex(P.dim))(ch.label.padEnd(20))} ${chalk.hex(P.muted)(ch.desc)}`,
1594
+ };
1595
+ });
1596
+ options.push({ value: "exit", label: `${chalk.hex(P.muted)("─")} Exit` });
1597
+
1598
+ const choice = await select({
1599
+ message: chalk.hex(P.cyan)("Select a channel for full setup details"),
1600
+ options,
1601
+ });
1602
+
1603
+ if (isCancel(choice) || choice === "exit") break;
1604
+
1605
+ const ch = CHANNEL_DEFS.find(c => c.name === choice);
1606
+ if (!ch) continue;
1607
+
1608
+ const isConfigured = ch.envRequired.every(e => {
1609
+ const [k, v] = e.split("=");
1610
+ return v ? process.env[k] === v : !!process.env[k];
1611
+ });
1612
+
1613
+ console.log(`\n${rowLine}`);
1614
+ console.log(` ${isConfigured ? chalk.hex(P.green)("✔") : chalk.hex(P.border)("○")} ${chalk.bold.hex(P.cyan)(ch.label)} ${chalk.hex(P.muted)(ch.desc)}`);
1615
+ console.log(` ${rowLine}`);
1616
+
1617
+ // Required env vars
1618
+ console.log(`\n ${chalk.bold.hex(P.teal)("Required env vars")}`);
1619
+ for (const env of ch.envRequired) {
1620
+ const [k] = env.split("=");
1621
+ const set = process.env[k];
1622
+ const status = set ? chalk.hex(P.green)("✔ set") : chalk.hex(P.red)("✘ not set");
1623
+ console.log(` ${S.bar} ${chalk.hex(P.amber)(env.padEnd(38))} ${status}`);
1624
+ }
1625
+
1626
+ // Optional env vars
1627
+ if (ch.envOptional.length > 0) {
1628
+ console.log(`\n ${chalk.bold.hex(P.teal)("Optional env vars")}`);
1629
+ for (const [env, desc] of ch.envOptional) {
1630
+ const set = process.env[env];
1631
+ const val = set ? chalk.hex(P.green)("✔ " + set.slice(0, 30) + (set.length > 30 ? "…" : "")) : chalk.hex(P.border)("not set");
1632
+ console.log(` ${S.bar} ${chalk.hex(P.dim)(env.padEnd(38))} ${val}`);
1633
+ console.log(` ${" ".repeat(38)} ${chalk.hex(P.border)(desc)}`);
1634
+ }
1635
+ }
1636
+
1637
+ // Setup steps
1638
+ console.log(`\n ${chalk.bold.hex(P.teal)("Setup")}`);
1639
+ for (const line of ch.setup) {
1640
+ console.log(` ${S.arrow} ${chalk.hex(P.dim)(line)}`);
1641
+ }
1642
+
1643
+ // Tenant configuration
1644
+ console.log(`\n ${chalk.bold.hex(P.teal)("Tenant / per-user config")}`);
1645
+ console.log(` ${S.bar} ${chalk.hex(P.muted)("View tenants on this channel:")}`);
1646
+ console.log(` ${chalk.hex(P.teal)("daemora tenant list")} ${chalk.hex(P.border)("(then filter by " + ch.name + ":)")}`);
1647
+ console.log(` ${S.bar} ${chalk.hex(P.muted)("Set per-tenant model override:")}`);
1648
+ console.log(` ${chalk.hex(P.teal)("daemora tenant set " + ch.tenantKey + ":<userId> model anthropic:claude-sonnet-4-6")}`);
1649
+ console.log(` ${S.bar} ${chalk.hex(P.muted)("Set per-tenant cost limit:")}`);
1650
+ console.log(` ${chalk.hex(P.teal)("daemora tenant set " + ch.tenantKey + ":<userId> maxDailyCost 1.00")}`);
1651
+ console.log(` ${S.bar} ${chalk.hex(P.muted)("Give tenant their own API key:")}`);
1652
+ console.log(` ${chalk.hex(P.teal)("daemora tenant apikey set " + ch.tenantKey + ":<userId> OPENAI_API_KEY sk-...")}`);
1653
+ console.log(` ${S.bar} ${chalk.hex(P.muted)("Suspend a tenant:")}`);
1654
+ console.log(` ${chalk.hex(P.teal)("daemora tenant suspend " + ch.tenantKey + ":<userId> \"reason\"")}`);
1655
+ console.log(` ${S.bar} ${chalk.hex(P.muted)("Store outbound channel credential:")}`);
1656
+ console.log(` ${chalk.hex(P.teal)("daemora tenant channel set " + ch.tenantKey + ":<userId> resend_api_key re_xxx")}`);
1657
+
1658
+ console.log(`\n${rowLine}\n`);
1659
+ }
1660
+
1661
+ console.log(` ${S.arrow} ${chalk.hex(P.teal)("daemora setup")} to configure channels interactively`);
1662
+ console.log(` ${S.arrow} Edit ${chalk.hex(P.teal)(".env")} and restart to apply changes\n`);
1663
+ }
1664
+
1665
+
1666
+ // ─── Models ──────────────────────────────────────────────────────────────────
1667
+
1668
+ async function handleModels() {
1669
+ const { select, isCancel } = await import("@clack/prompts");
1670
+ const w = 67;
1671
+ const line = chalk.hex(P.cyan)("━".repeat(w));
1672
+ const rowLine = chalk.hex(P.border)("─".repeat(w));
1673
+
1674
+ const PROVIDERS = [
1675
+ {
1676
+ name: "OpenAI", prefix: "openai", envKey: "OPENAI_API_KEY",
1677
+ models: [
1678
+ // GPT-5 family
1679
+ { id: "gpt-5.3-codex", desc: "Latest coding model (2025)", isNew: true },
1680
+ { id: "gpt-5.2-pro", desc: "GPT-5.2 Pro — highest capability", isNew: true },
1681
+ { id: "gpt-5.2", desc: "GPT-5.2 flagship (Dec 2025)", isNew: true },
1682
+ { id: "gpt-5.1-codex-max", desc: "GPT-5.1 Codex Max — coding (Nov 2025)", isNew: true },
1683
+ { id: "gpt-5.1", desc: "GPT-5.1 (Nov 2025)", isNew: true },
1684
+ { id: "gpt-5-pro", desc: "GPT-5 Pro — most powerful" },
1685
+ { id: "gpt-5", desc: "GPT-5 flagship (Aug 2025)" },
1686
+ { id: "gpt-5-mini", desc: "GPT-5 Mini — fast & cheap" },
1687
+ { id: "gpt-5-nano", desc: "GPT-5 Nano — cheapest GPT-5" },
1688
+ // o-series reasoning
1689
+ { id: "o3-pro", desc: "Best reasoning — most thorough" },
1690
+ { id: "o3-deep-research", desc: "Deep research with web browsing" },
1691
+ { id: "o4-mini-deep-research", desc: "Fast deep research" },
1692
+ { id: "o3", desc: "Advanced reasoning (Apr 2025)" },
1693
+ { id: "o4-mini", desc: "Fast reasoning (Apr 2025)" },
1694
+ { id: "o1-pro", desc: "o1 Pro — powerful reasoning (Mar 2025)" },
1695
+ { id: "o1", desc: "o1 reasoning model" },
1696
+ { id: "o3-mini", desc: "Lightweight reasoning" },
1697
+ // GPT-4.1 (1M context)
1698
+ { id: "gpt-4.1", desc: "1M context, best instruction following" },
1699
+ { id: "gpt-4.1-mini", desc: "1M context, fast & affordable (default)" },
1700
+ { id: "gpt-4.1-nano", desc: "1M context, fastest & cheapest" },
1701
+ // GPT-4o & specialized
1702
+ { id: "gpt-4o", desc: "Vision + text (128K ctx)" },
1703
+ { id: "gpt-4o-mini", desc: "GPT-4o Mini (128K ctx)" },
1704
+ { id: "computer-use-preview", desc: "Computer use / GUI automation" },
1705
+ { id: "gpt-4o-search-preview", desc: "Built-in live web search" },
1706
+ { id: "gpt-image-1", desc: "Image generation (native API)" },
1707
+ ],
1708
+ },
1709
+ {
1710
+ name: "Anthropic", prefix: "anthropic", envKey: "ANTHROPIC_API_KEY",
1711
+ models: [
1712
+ { id: "claude-opus-4-6", desc: "Most intelligent — complex reasoning + extended thinking", isNew: true },
1713
+ { id: "claude-sonnet-4-6", desc: "Best speed/intelligence — daily coding & agents", isNew: true },
1714
+ { id: "claude-haiku-4-5", desc: "Fastest — high-volume, cost-sensitive tasks" },
1715
+ { id: "claude-opus-4-5-20251101", desc: "Opus 4.5 — complex multi-step tasks (Nov 2025)" },
1716
+ { id: "claude-sonnet-4-5-20250929", desc: "Sonnet 4.5 — coding & agentic tasks (200K ctx)" },
1717
+ { id: "claude-opus-4-1-20250805", desc: "Opus 4.1 — long-duration complex tasks" },
1718
+ { id: "claude-3-5-sonnet-latest", desc: "3.5 Sonnet — previous gen, widely used" },
1719
+ { id: "claude-3-5-haiku-latest", desc: "3.5 Haiku — fast previous gen" },
1720
+ ],
1721
+ },
1722
+ {
1723
+ name: "Google", prefix: "google", envKey: "GOOGLE_AI_API_KEY",
1724
+ models: [
1725
+ { id: "gemini-3.1-pro-preview", desc: "Latest — complex tasks, advanced reasoning", isNew: true },
1726
+ { id: "gemini-3.1-flash-lite-preview", desc: "Latest — cost-efficient & fast", isNew: true },
1727
+ { id: "gemini-2.5-pro", desc: "GA — complex reasoning & coding (1M ctx)" },
1728
+ { id: "gemini-2.5-flash", desc: "Fast & cost-effective for high-volume tasks" },
1729
+ { id: "gemini-2.5-flash-lite", desc: "Speed-optimised for high-throughput" },
1730
+ { id: "gemini-live-2.5-flash-native-audio", desc: "Real-time bidirectional audio/video agents" },
1731
+ { id: "gemini-2.0-flash", desc: "Previous gen flash" },
1732
+ ],
1733
+ },
1734
+ {
1735
+ name: "xAI", prefix: "xai", envKey: "XAI_API_KEY",
1736
+ models: [
1737
+ { id: "grok-4", desc: "Grok 4 — latest & most capable (Jul 2025)", isNew: true },
1738
+ { id: "grok-3-beta", desc: "Grok 3 Beta — 131K ctx" },
1739
+ { id: "grok-3-mini-beta", desc: "Grok 3 Mini — fast, 131K ctx" },
1740
+ ],
1741
+ },
1742
+ {
1743
+ name: "DeepSeek", prefix: "deepseek", envKey: "DEEPSEEK_API_KEY",
1744
+ models: [
1745
+ { id: "deepseek-chat", desc: "DeepSeek V3 — excellent coder (128K ctx)" },
1746
+ { id: "deepseek-reasoner", desc: "DeepSeek R1 — chain-of-thought reasoning" },
1747
+ ],
1748
+ },
1749
+ {
1750
+ name: "Mistral", prefix: "mistral", envKey: "MISTRAL_API_KEY",
1751
+ models: [
1752
+ { id: "mistral-large-2512", desc: "Flagship — best quality (Dec 2025)", isNew: true },
1753
+ { id: "mistral-medium-3", desc: "Balanced capability & speed (May 2025)" },
1754
+ { id: "codestral-2508", desc: "Code specialist (Aug 2025)" },
1755
+ { id: "mistral-small-3.2-24b", desc: "Lightweight, runs locally (24B params)" },
1756
+ ],
1757
+ },
1758
+ {
1759
+ name: "Ollama (local)", prefix: "ollama", configured: true,
1760
+ models: [
1761
+ { id: "llama4-maverick", desc: "Llama 4 Maverick — 17B MoE, 1M ctx, multimodal", isNew: true },
1762
+ { id: "llama4-scout", desc: "Llama 4 Scout — 17B MoE, 10M ctx", isNew: true },
1763
+ { id: "llama3.3", desc: "Llama 3.3 70B — best open model (Dec 2024)" },
1764
+ { id: "qwen2.5", desc: "Qwen 2.5 72B — strong coder" },
1765
+ { id: "deepseek-r1", desc: "DeepSeek-R1 local — reasoning" },
1766
+ { id: "mistral", desc: "Mistral 7B — fast small model" },
1767
+ { id: "phi4", desc: "Phi-4 14B — Microsoft small model" },
1768
+ { id: "codellama", desc: "CodeLlama — code specialised" },
1769
+ ],
1770
+ },
1771
+ ];
1772
+
1773
+ const routingRows = [
1774
+ ["DEFAULT_MODEL", process.env.DEFAULT_MODEL || chalk.hex(P.muted)("openai:gpt-4.1-mini (built-in default)")],
1775
+ ["CODE_MODEL", process.env.CODE_MODEL || chalk.hex(P.border)("not set — uses DEFAULT_MODEL")],
1776
+ ["RESEARCH_MODEL", process.env.RESEARCH_MODEL || chalk.hex(P.border)("not set — uses DEFAULT_MODEL")],
1777
+ ["WRITER_MODEL", process.env.WRITER_MODEL || chalk.hex(P.border)("not set — uses DEFAULT_MODEL")],
1778
+ ["ANALYST_MODEL", process.env.ANALYST_MODEL || chalk.hex(P.border)("not set — uses DEFAULT_MODEL")],
1779
+ ];
1780
+
1781
+ function renderProvider(prov) {
1782
+ const configured = prov.configured || !!process.env[prov.envKey];
1783
+ const status = configured
1784
+ ? chalk.hex(P.green)("✔") + " " + chalk.bold.hex(P.teal)(prov.name) + chalk.hex(P.muted)(` [${prov.prefix}:]`) + chalk.hex(P.green)(" configured")
1785
+ : chalk.hex(P.red)("✘") + " " + chalk.bold.hex(P.dim)(prov.name) + chalk.hex(P.border)(` [${prov.prefix}:]`) + chalk.hex(P.border)(" not configured");
1786
+
1787
+ console.log(`\n ${status}`);
1788
+ if (!configured && prov.envKey) {
1789
+ console.log(` ${chalk.hex(P.border)("env: ")}${chalk.hex(P.amber)(prov.envKey)}`);
1790
+ }
1791
+ console.log(` ${chalk.hex(P.border)("─".repeat(65))}`);
1792
+ for (const m of prov.models) {
1793
+ const fullId = `${prov.prefix}:${m.id}`;
1794
+ const newBadge = m.isNew ? chalk.hex(P.amber)(" [NEW]") : "";
1795
+ console.log(` ${S.dot} ${chalk.hex(P.teal)(fullId.padEnd(50))}${newBadge}`);
1796
+ console.log(` ${chalk.hex(P.dim)(m.desc)}`);
1797
+ }
1798
+ }
1799
+
1800
+ // ── Header ────────────────────────────────────────────────────────────────
1801
+ console.log(`\n${line}`);
1802
+ console.log(` ${chalk.bold.hex(P.cyan)("Daemora Model Providers")} ${chalk.hex(P.muted)(PROVIDERS.length + " providers · " + PROVIDERS.reduce((s,p) => s + p.models.length, 0) + " models")}`);
1803
+ console.log(rowLine);
1804
+
1805
+ // ── Interactive provider browser ──────────────────────────────────────────
1806
+ while (true) {
1807
+ const choices = PROVIDERS.map(p => {
1808
+ const configured = p.configured || !!process.env[p.envKey];
1809
+ const badge = configured ? chalk.hex(P.green)("✔") : chalk.hex(P.border)("○");
1810
+ return {
1811
+ value: p.prefix,
1812
+ label: `${badge} ${p.name.padEnd(18)} ${chalk.hex(P.muted)(p.models.length + " models")}`,
1813
+ };
1814
+ });
1815
+ choices.push({ value: "routing", label: `${S.star} Task-Type Routing` });
1816
+ choices.push({ value: "exit", label: `${chalk.hex(P.muted)("─")} Exit` });
1817
+
1818
+ console.log();
1819
+ const choice = await select({
1820
+ message: chalk.hex(P.cyan)("Select a provider to browse models"),
1821
+ options: choices,
1822
+ });
1823
+
1824
+ if (isCancel(choice) || choice === "exit") break;
1825
+
1826
+ if (choice === "routing") {
1827
+ console.log(`\n${rowLine}`);
1828
+ console.log(` ${chalk.bold.hex(P.teal)("Task-Type Model Routing")}`);
1829
+ console.log(` ${chalk.hex(P.border)("─".repeat(65))}`);
1830
+ for (const [k, v] of routingRows) {
1831
+ console.log(` ${S.bar} ${chalk.hex(P.muted)(k.padEnd(16))} ${chalk.hex(P.teal)(String(v))}`);
1832
+ }
1833
+ console.log(`\n ${chalk.hex(P.dim)("Set via env vars. Sub-agents auto-pick model by profile (coder/researcher/...)")}`);
1834
+ continue;
1835
+ }
1836
+
1837
+ const prov = PROVIDERS.find(p => p.prefix === choice);
1838
+ if (prov) renderProvider(prov);
1839
+ }
1840
+
1841
+ console.log(`\n${rowLine}`);
1842
+ console.log(` ${S.arrow} ${chalk.hex(P.teal)("daemora setup")} choose provider interactively`);
1843
+ console.log(` ${S.arrow} ${chalk.hex(P.teal)("DEFAULT_MODEL=... daemora start")} override at startup\n`);
1844
+ }
1845
+
1846
+ // ─── Tools ────────────────────────────────────────────────────────────────────
1847
+
1848
+ async function handleTools(filter) {
1849
+ const { select, isCancel } = await import("@clack/prompts");
1850
+ const w = 67;
1851
+ const line = chalk.hex(P.cyan)("━".repeat(w));
1852
+ const rowLine = chalk.hex(P.border)("─".repeat(w));
1853
+
1854
+ const TOOLS = [
1855
+ { name: "readFile", cat: "Files", desc: "Read files from disk" },
1856
+ { name: "writeFile", cat: "Files", desc: "Write/create files" },
1857
+ { name: "editFile", cat: "Files", desc: "Edit files (search & replace)" },
1858
+ { name: "listDirectory", cat: "Files", desc: "List directory contents" },
1859
+ { name: "searchFiles", cat: "Files", desc: "Find files by name/pattern" },
1860
+ { name: "searchContent", cat: "Files", desc: "Search file content (ripgrep-style)" },
1861
+ { name: "glob", cat: "Files", desc: "Glob file pattern matching" },
1862
+ { name: "grep", cat: "Files", desc: "Regex search in files" },
1863
+ { name: "applyPatch", cat: "Files", desc: "Apply unified diff patches" },
1864
+ { name: "executeCommand", cat: "System", desc: "Run shell commands (sandboxed)" },
1865
+ { name: "sshTool", cat: "System", desc: "SSH remote exec & SCP file transfer" },
1866
+ { name: "database", cat: "System", desc: "Query SQLite / PostgreSQL / MySQL" },
1867
+ { name: "webFetch", cat: "Web", desc: "Fetch any URL content" },
1868
+ { name: "webSearch", cat: "Web", desc: "Search the web (SerpAPI/Brave)" },
1869
+ { name: "browserAction", cat: "Web", desc: "Browser automation (Playwright)" },
1870
+ { name: "googlePlaces", cat: "Web", desc: "Search places via Google Places API" },
1871
+ { name: "sendEmail", cat: "Communication", desc: "Send emails via SMTP/Resend" },
1872
+ { name: "messageChannel", cat: "Communication", desc: "Send message to any active channel" },
1873
+ { name: "makeVoiceCall", cat: "Communication", desc: "Make voice calls (Twilio)" },
1874
+ { name: "iMessageTool", cat: "Communication", desc: "Send/read iMessages (macOS)" },
1875
+ { name: "readPDF", cat: "Media", desc: "Extract text from PDF files" },
1876
+ { name: "generateImage", cat: "Media", desc: "Generate images (DALL-E 3)" },
1877
+ { name: "imageAnalysis", cat: "Media", desc: "Analyze images with vision AI" },
1878
+ { name: "transcribeAudio", cat: "Media", desc: "Transcribe audio files (Whisper)" },
1879
+ { name: "textToSpeech", cat: "Media", desc: "Convert text to speech (TTS)" },
1880
+ { name: "screenCapture", cat: "Media", desc: "Capture screen / screenshots" },
1881
+ { name: "sendFile", cat: "Media", desc: "Send files via channels" },
1882
+ { name: "createDocument", cat: "Media", desc: "Create formatted documents" },
1883
+ { name: "gitTool", cat: "Dev", desc: "Git operations (clone/commit/push/...)" },
1884
+ { name: "clipboard", cat: "Dev", desc: "Read/write system clipboard" },
1885
+ { name: "readMemory", cat: "Memory", desc: "Read agent memory file" },
1886
+ { name: "writeMemory", cat: "Memory", desc: "Write/update agent memory" },
1887
+ { name: "searchMemory", cat: "Memory", desc: "Semantic search in memory" },
1888
+ { name: "readDailyLog", cat: "Memory", desc: "Read daily activity log" },
1889
+ { name: "writeDailyLog", cat: "Memory", desc: "Append to daily log" },
1890
+ { name: "pruneMemory", cat: "Memory", desc: "Compact/prune old memories" },
1891
+ { name: "listMemoryCategories", cat: "Memory", desc: "List memory categories" },
1892
+ { name: "spawnAgent", cat: "Agents", desc: "Spawn a sub-agent for a task" },
1893
+ { name: "parallelAgents", cat: "Agents", desc: "Spawn multiple agents in parallel" },
1894
+ { name: "delegateToAgent", cat: "Agents", desc: "Delegate to a remote A2A agent" },
1895
+ { name: "manageAgents", cat: "Agents", desc: "List/kill/steer running agents" },
1896
+ { name: "manageMCP", cat: "MCP", desc: "Manage MCP server connections" },
1897
+ { name: "useMCP", cat: "MCP", desc: "Call any MCP tool by name" },
1898
+ { name: "projectTracker", cat: "Productivity", desc: "Track projects, tasks, milestones" },
1899
+ { name: "cron", cat: "Productivity", desc: "Schedule recurring tasks" },
1900
+ { name: "notification", cat: "Productivity", desc: "Desktop/push notifications (ntfy/Pushover)" },
1901
+ { name: "calendar", cat: "Productivity", desc: "Read/create calendar events (macOS/Google)" },
1902
+ { name: "contacts", cat: "Productivity", desc: "Search macOS / Google contacts" },
1903
+ { name: "philipsHue", cat: "Smart Home", desc: "Control Philips Hue lights" },
1904
+ { name: "sonos", cat: "Smart Home", desc: "Control Sonos speakers" },
1905
+ ];
1906
+
1907
+ // ── Header ────────────────────────────────────────────────────────────────
1908
+ console.log(`\n${line}`);
1909
+ console.log(` ${chalk.bold.hex(P.cyan)("Daemora Tools")} ${chalk.hex(P.muted)(TOOLS.length + " built-in tools")}`);
1910
+ console.log(rowLine);
1911
+
1912
+ // ── Filter mode (daemora tools <keyword>) ─────────────────────────────────
1913
+ if (filter) {
1914
+ const fl = filter.toLowerCase();
1915
+ const results = TOOLS.filter(t =>
1916
+ t.name.toLowerCase().includes(fl) ||
1917
+ t.cat.toLowerCase().includes(fl) ||
1918
+ t.desc.toLowerCase().includes(fl),
1919
+ );
1920
+ console.log(`\n ${chalk.hex(P.amber)("Filter:")} ${chalk.bold(filter)} ${chalk.hex(P.muted)(results.length + " match" + (results.length !== 1 ? "es" : ""))}\n`);
1921
+ for (const tool of results) {
1922
+ console.log(` ${S.dot} ${chalk.hex(P.teal)(tool.name.padEnd(26))} ${chalk.hex(P.dim)(tool.cat.padEnd(14))} ${chalk.hex(P.muted)(tool.desc)}`);
1923
+ }
1924
+ console.log();
1925
+ return;
1926
+ }
1927
+
1928
+ // ── Group by category ─────────────────────────────────────────────────────
1929
+ const byCategory = {};
1930
+ for (const tool of TOOLS) {
1931
+ (byCategory[tool.cat] = byCategory[tool.cat] || []).push(tool);
1932
+ }
1933
+ const categories = Object.keys(byCategory);
1934
+
1935
+ // ── Interactive category browser ──────────────────────────────────────────
1936
+ while (true) {
1937
+ const catChoices = categories.map(cat => ({
1938
+ value: cat,
1939
+ label: `${chalk.hex(P.teal)(cat.padEnd(16))} ${chalk.hex(P.muted)(byCategory[cat].length + " tools")}`,
1940
+ }));
1941
+ catChoices.push({ value: "all", label: `${chalk.hex(P.cyan)("◆")} All tools (${TOOLS.length})` });
1942
+ catChoices.push({ value: "exit", label: `${chalk.hex(P.muted)("─")} Exit` });
1943
+
1944
+ console.log();
1945
+ const choice = await select({
1946
+ message: chalk.hex(P.cyan)("Browse tools by category"),
1947
+ options: catChoices,
1948
+ });
1949
+
1950
+ if (isCancel(choice) || choice === "exit") break;
1951
+
1952
+ const toolList = choice === "all" ? TOOLS : byCategory[choice];
1953
+
1954
+ console.log(`\n ${chalk.bold.hex(P.teal)(choice === "all" ? "All Tools" : choice)} ${chalk.hex(P.muted)("(" + toolList.length + ")")}`);
1955
+ console.log(` ${chalk.hex(P.border)("─".repeat(65))}`);
1956
+ for (const tool of toolList) {
1957
+ const cat = choice === "all" ? chalk.hex(P.border)(tool.cat.padEnd(14) + " ") : "";
1958
+ console.log(` ${S.dot} ${chalk.hex(P.teal)(tool.name.padEnd(26))} ${cat}${chalk.hex(P.dim)(tool.desc)}`);
1959
+ }
1960
+ }
1961
+
1962
+ console.log(`\n${rowLine}`);
1963
+ console.log(` ${S.arrow} ${chalk.hex(P.teal)("daemora tools Files")} filter by category name`);
1964
+ console.log(` ${S.arrow} ${chalk.hex(P.teal)("daemora mcp list")} see connected MCP server tools\n`);
1965
+ }
1966
+
1967
+
1189
1968
  function printHelp() {
1190
1969
  const w = 56;
1191
1970
  const line = chalk.hex(P.brand)("\u2501".repeat(w));
@@ -1193,7 +1972,7 @@ function printHelp() {
1193
1972
 
1194
1973
  console.log(`
1195
1974
  ${line}
1196
- ${t.h("Daemora")} ${t.muted("Your 24/7 AI Digital Worker")}
1975
+ ${t.h("Daemora")} ${t.muted("Your 24/7 AI Agent")}
1197
1976
  ${line}
1198
1977
 
1199
1978
  ${t.bold("USAGE")}
@@ -1244,16 +2023,28 @@ ${line}
1244
2023
  ${t.cmd("tenant unsuspend")} ${t.dim("<id>")} Unsuspend a tenant
1245
2024
  ${t.cmd("tenant reset")} ${t.dim("<id>")} Reset tenant config (keep cost history)
1246
2025
  ${t.cmd("tenant delete")} ${t.dim("<id>")} Delete a tenant record
1247
- ${t.cmd("tenant apikey set")} ${t.dim("<id> <KEY> <val>")} Store encrypted API key for tenant
2026
+ ${t.cmd("tenant apikey set")} ${t.dim("<id> <KEY> <val>")} Store encrypted AI provider key (OPENAI_API_KEY, etc.)
1248
2027
  ${t.cmd("tenant apikey delete")} ${t.dim("<id> <KEY>")} Delete a tenant API key
1249
2028
  ${t.cmd("tenant apikey list")} ${t.dim("<id>")} List tenant API key names (not values)
2029
+ ${t.cmd("tenant channel set")} ${t.dim("<id> <key> <val>")} Store encrypted outbound channel credential
2030
+ ${t.cmd("tenant channel unset")} ${t.dim("<id> <key>")} Remove a channel credential
2031
+ ${t.cmd("tenant channel list")} ${t.dim("<id>")} List stored channel credential keys
2032
+ ${t.muted(" channel keys: email email_password resend_api_key resend_from")}
2033
+
2034
+ ${t.cmd("channels")} List all 19 supported channels + setup status
2035
+ ${t.cmd("models")} List all model providers + task-type routing
2036
+ ${t.cmd("tools")} ${t.dim("[filter]")} List all 50 built-in tools (filter by name/category)
1250
2037
 
1251
- ${t.cmd("doctor")} Security audit check for misconfigurations
2038
+ ${t.cmd("doctor")} Security audit - check for misconfigurations
1252
2039
 
1253
2040
  ${t.cmd("help")} Show this help
1254
2041
 
1255
2042
  ${t.bold("EXAMPLES")}
1256
2043
  ${dimLine}
2044
+ ${t.dim("$")} daemora channels
2045
+ ${t.dim("$")} daemora models
2046
+ ${t.dim("$")} daemora tools
2047
+ ${t.dim("$")} daemora tools Files
1257
2048
  ${t.dim("$")} daemora setup
1258
2049
  ${t.dim("$")} daemora start
1259
2050
  ${t.dim("$")} daemora daemon install
@@ -1265,7 +2056,7 @@ ${line}
1265
2056
  ${t.dim("$")} daemora mcp add notion http://localhost:3100/mcp
1266
2057
  ${t.dim("$")} daemora mcp add myserver http://localhost:3100/sse --sse
1267
2058
  ${t.dim("$")} daemora mcp remove github
1268
- ${t.dim("$")} daemora mcp add (interactive prompts for everything)
2059
+ ${t.dim("$")} daemora mcp add (interactive - prompts for everything)
1269
2060
  ${t.dim("$")} daemora mcp reload github (reconnects live if agent running)
1270
2061
  ${t.dim("$")} daemora sandbox add ~/Downloads (lock agent to Downloads folder)
1271
2062
  ${t.dim("$")} daemora sandbox block ~/Downloads/private
@@ -1280,6 +2071,12 @@ ${line}
1280
2071
  ${t.dim("$")} daemora tenant apikey set telegram:123 OPENAI_API_KEY sk-...
1281
2072
  ${t.dim("$")} daemora tenant apikey list telegram:123
1282
2073
  ${t.dim("$")} daemora tenant apikey delete telegram:123 OPENAI_API_KEY
2074
+ ${t.dim("$")} daemora tenant channel set telegram:123 resend_api_key re_xxx
2075
+ ${t.dim("$")} daemora tenant channel set telegram:123 resend_from you@yourdomain.com
2076
+ ${t.dim("$")} daemora tenant channel set telegram:123 email you@gmail.com
2077
+ ${t.dim("$")} daemora tenant channel set telegram:123 email_password xxxx-xxxx-xxxx-xxxx
2078
+ ${t.dim("$")} daemora tenant channel list telegram:123
2079
+ ${t.dim("$")} daemora tenant channel unset telegram:123 email_password
1283
2080
  ${t.dim("$")} daemora doctor
1284
2081
  `);
1285
2082
  }