daemora 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +106 -76
- package/SOUL.md +100 -28
- package/config/mcp.json +9 -9
- package/package.json +15 -8
- package/skills/apple-notes.md +0 -52
- package/skills/apple-reminders.md +1 -87
- package/skills/camsnap.md +20 -144
- package/skills/coding.md +7 -7
- package/skills/documents.md +6 -6
- package/skills/email.md +6 -6
- package/skills/gif-search.md +28 -171
- package/skills/healthcheck.md +21 -203
- package/skills/image-gen.md +24 -123
- package/skills/model-usage.md +18 -165
- package/skills/obsidian.md +28 -174
- package/skills/pdf.md +30 -181
- package/skills/research.md +6 -6
- package/skills/skill-creator.md +35 -111
- package/skills/spotify.md +2 -17
- package/skills/summarize.md +36 -193
- package/skills/things.md +23 -175
- package/skills/tmux.md +1 -91
- package/skills/trello.md +32 -157
- package/skills/video-frames.md +26 -166
- package/skills/weather.md +6 -6
- package/src/a2a/A2AClient.js +2 -2
- package/src/a2a/A2AServer.js +6 -6
- package/src/a2a/AgentCard.js +2 -2
- package/src/agents/SubAgentManager.js +61 -19
- package/src/agents/Supervisor.js +4 -4
- package/src/channels/BaseChannel.js +6 -6
- package/src/channels/BlueBubblesChannel.js +112 -0
- package/src/channels/DiscordChannel.js +8 -8
- package/src/channels/EmailChannel.js +54 -26
- package/src/channels/FeishuChannel.js +140 -0
- package/src/channels/GoogleChatChannel.js +8 -8
- package/src/channels/HttpChannel.js +2 -2
- package/src/channels/IRCChannel.js +144 -0
- package/src/channels/LineChannel.js +13 -13
- package/src/channels/MatrixChannel.js +97 -0
- package/src/channels/MattermostChannel.js +119 -0
- package/src/channels/NextcloudChannel.js +133 -0
- package/src/channels/NostrChannel.js +175 -0
- package/src/channels/SignalChannel.js +9 -9
- package/src/channels/SlackChannel.js +10 -10
- package/src/channels/TeamsChannel.js +10 -10
- package/src/channels/TelegramChannel.js +8 -8
- package/src/channels/TwitchChannel.js +128 -0
- package/src/channels/WhatsAppChannel.js +10 -10
- package/src/channels/ZaloChannel.js +119 -0
- package/src/channels/iMessageChannel.js +150 -0
- package/src/channels/index.js +241 -11
- package/src/cli.js +835 -38
- package/src/config/agentProfiles.js +19 -19
- package/src/config/channels.js +1 -1
- package/src/config/default.js +12 -7
- package/src/config/models.js +3 -3
- package/src/config/permissions.js +2 -2
- package/src/core/AgentLoop.js +13 -13
- package/src/core/Compaction.js +3 -3
- package/src/core/CostTracker.js +2 -2
- package/src/core/EventBus.js +15 -15
- package/src/core/TaskQueue.js +24 -7
- package/src/core/TaskRunner.js +19 -6
- package/src/daemon/DaemonManager.js +4 -4
- package/src/hooks/HookRunner.js +4 -4
- package/src/index.js +6 -2
- package/src/mcp/MCPAgentRunner.js +3 -3
- package/src/mcp/MCPClient.js +9 -9
- package/src/mcp/MCPManager.js +14 -14
- package/src/models/ModelRouter.js +2 -2
- package/src/safety/AuditLog.js +3 -3
- package/src/safety/CircuitBreaker.js +2 -2
- package/src/safety/CommandGuard.js +132 -0
- package/src/safety/FilesystemGuard.js +23 -3
- package/src/safety/GitRollback.js +5 -5
- package/src/safety/HumanApproval.js +9 -9
- package/src/safety/InputSanitizer.js +81 -8
- package/src/safety/PermissionGuard.js +2 -2
- package/src/safety/Sandbox.js +1 -1
- package/src/safety/SecretScanner.js +90 -28
- package/src/safety/SecretVault.js +2 -2
- package/src/scheduler/Heartbeat.js +3 -3
- package/src/scheduler/Scheduler.js +6 -6
- package/src/setup/theme.js +171 -66
- package/src/setup/wizard.js +432 -57
- package/src/skills/SkillLoader.js +145 -8
- package/src/storage/TaskStore.js +39 -15
- package/src/systemPrompt.js +45 -43
- package/src/tenants/TenantManager.js +79 -22
- package/src/tools/ToolRegistry.js +3 -3
- package/src/tools/applyPatch.js +2 -2
- package/src/tools/browserAutomation.js +4 -4
- package/src/tools/calendar.js +155 -0
- package/src/tools/clipboard.js +71 -0
- package/src/tools/contacts.js +138 -0
- package/src/tools/createDocument.js +2 -2
- package/src/tools/cronTool.js +14 -14
- package/src/tools/database.js +165 -0
- package/src/tools/editFile.js +10 -10
- package/src/tools/executeCommand.js +11 -3
- package/src/tools/generateImage.js +79 -0
- package/src/tools/gitTool.js +141 -0
- package/src/tools/glob.js +1 -1
- package/src/tools/googlePlaces.js +136 -0
- package/src/tools/grep.js +2 -2
- package/src/tools/iMessageTool.js +86 -0
- package/src/tools/imageAnalysis.js +3 -3
- package/src/tools/index.js +56 -2
- package/src/tools/makeVoiceCall.js +283 -0
- package/src/tools/manageAgents.js +2 -2
- package/src/tools/manageMCP.js +38 -20
- package/src/tools/memory.js +25 -32
- package/src/tools/messageChannel.js +1 -1
- package/src/tools/notification.js +90 -0
- package/src/tools/philipsHue.js +147 -0
- package/src/tools/projectTracker.js +8 -8
- package/src/tools/readFile.js +1 -1
- package/src/tools/readPDF.js +73 -0
- package/src/tools/screenCapture.js +6 -6
- package/src/tools/searchContent.js +2 -2
- package/src/tools/searchFiles.js +1 -1
- package/src/tools/sendEmail.js +79 -24
- package/src/tools/sendFile.js +4 -4
- package/src/tools/sonos.js +137 -0
- package/src/tools/sshTool.js +130 -0
- package/src/tools/textToSpeech.js +5 -5
- package/src/tools/transcribeAudio.js +4 -4
- package/src/tools/useMCP.js +4 -4
- package/src/tools/webFetch.js +2 -2
- package/src/tools/webSearch.js +1 -1
- package/src/utils/Embeddings.js +79 -0
- package/src/voice/VoiceSessionManager.js +170 -0
- package/src/voice/VoiceWebhook.js +188 -0
package/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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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:
|
|
32
|
-
accent:
|
|
33
|
-
success: (s) => chalk.hex(P.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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:
|
|
44
|
-
cross:
|
|
45
|
-
arrow:
|
|
46
|
-
dot:
|
|
47
|
-
bar:
|
|
48
|
-
info:
|
|
49
|
-
lock:
|
|
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
|
|
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
|
|
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
|
|
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>
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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)
|
|
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)
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
}
|