daemora 1.0.2 → 1.0.4

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 +834 -37
  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 +13 -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
@@ -2,7 +2,7 @@ import { blockedCommands } from "../config/permissions.js";
2
2
  import eventBus from "../core/EventBus.js";
3
3
 
4
4
  /**
5
- * Sandbox command execution safety.
5
+ * Sandbox - command execution safety.
6
6
  *
7
7
  * Two modes:
8
8
  * 1. Blocklist (default): Block known dangerous commands via regex patterns.
@@ -1,31 +1,87 @@
1
1
  import eventBus from "../core/EventBus.js";
2
2
 
3
3
  /**
4
- * Secret Scanner detects and redacts sensitive data in tool I/O.
4
+ * Secret Scanner - detects and redacts sensitive data in tool I/O.
5
5
  *
6
- * Scans for: API keys, private keys, passwords, AWS keys, tokens,
7
- * connection strings, .env contents.
6
+ * Two complementary layers:
7
+ *
8
+ * 1. Pattern-based redaction — regex patterns for known secret formats
9
+ * (AWS keys, GitHub tokens, OpenAI keys, JWTs, connection strings, etc.)
10
+ *
11
+ * 2. Blind env-var redaction — at startup, collect all process.env values whose
12
+ * names look like secrets (contain _KEY, _TOKEN, _SECRET, etc.) and redact
13
+ * their exact values from any tool output. This catches custom tokens like
14
+ * TELEGRAM_BOT_TOKEN, TWILIO_AUTH_TOKEN, DISCORD_BOT_TOKEN that don't match
15
+ * any known format pattern.
8
16
  */
9
17
 
10
18
  const SECRET_PATTERNS = [
11
- { name: "AWS Access Key", pattern: /AKIA[0-9A-Z]{16}/g },
12
- { name: "AWS Secret Key", pattern: /(?:aws_secret_access_key|secret_key)\s*[:=]\s*['"]?([A-Za-z0-9/+=]{40})['"]?/gi },
13
- { name: "Generic API Key", pattern: /(?:api[_-]?key|apikey)\s*[:=]\s*['"]?([A-Za-z0-9_\-]{20,})['"]?/gi },
14
- { name: "Generic Secret", pattern: /(?:secret|password|passwd|pwd|token)\s*[:=]\s*['"]?([^\s'"]{8,})['"]?/gi },
15
- { name: "Private Key", pattern: /-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/g },
16
- { name: "GitHub Token", pattern: /gh[ps]_[A-Za-z0-9_]{36,}/g },
17
- { name: "Slack Token", pattern: /xox[bprs]-[A-Za-z0-9\-]{10,}/g },
18
- { name: "OpenAI Key", pattern: /sk-[A-Za-z0-9]{20,}/g },
19
- { name: "Bearer Token", pattern: /Bearer\s+[A-Za-z0-9\-._~+\/]{20,}/g },
20
- { name: "Connection String", pattern: /(?:mongodb|postgres|mysql|redis):\/\/[^\s'"]+/gi },
21
- { name: "JWT", pattern: /eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g },
19
+ { name: "AWS Access Key", pattern: /AKIA[0-9A-Z]{16}/g },
20
+ { name: "AWS Secret Key", pattern: /(?:aws_secret_access_key|secret_key)\s*[:=]\s*['"]?([A-Za-z0-9/+=]{40})['"]?/gi },
21
+ { name: "Generic API Key", pattern: /(?:api[_-]?key|apikey)\s*[:=]\s*['"]?([A-Za-z0-9_\-]{20,})['"]?/gi },
22
+ { name: "Generic Secret", pattern: /(?:secret|password|passwd|pwd|token)\s*[:=]\s*['"]?([^\s'"]{8,})['"]?/gi },
23
+ { name: "Private Key", pattern: /-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/g },
24
+ { name: "GitHub Token", pattern: /gh[ps]_[A-Za-z0-9_]{36,}/g },
25
+ { name: "Slack Token", pattern: /xox[bprs]-[A-Za-z0-9\-]{10,}/g },
26
+ { name: "OpenAI Key", pattern: /sk-[A-Za-z0-9]{20,}/g },
27
+ { name: "Anthropic Key", pattern: /sk-ant-[A-Za-z0-9\-_]{20,}/g },
28
+ { name: "Google AI Key", pattern: /AIza[A-Za-z0-9\-_]{35}/g },
29
+ { name: "Telegram Token", pattern: /\d{8,12}:[A-Za-z0-9_\-]{35}/g },
30
+ { name: "Twilio SID", pattern: /AC[a-f0-9]{32}/g },
31
+ { name: "Bearer Token", pattern: /Bearer\s+[A-Za-z0-9\-._~+\/]{20,}/g },
32
+ { name: "Connection String", pattern: /(?:mongodb|postgres|mysql|redis):\/\/[^\s'"]+/gi },
33
+ { name: "JWT", pattern: /eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g },
22
34
  ];
23
35
 
36
+ // ── Blind env-var redaction setup ─────────────────────────────────────────────
37
+ // Collect values of env vars whose NAMES suggest they hold secrets.
38
+ // Done once at module load — captures all secrets including ones that don't
39
+ // match any regex pattern above (e.g. DISCORD_BOT_TOKEN, LINE_CHANNEL_SECRET).
40
+ const SENSITIVE_NAME_PATTERN = /(_KEY|_TOKEN|_SECRET|_PASSWORD|_PASS|_PWD|_SID|_AUTH|_PRIVATE|_CREDENTIAL|_API|_WEBHOOK)$/i;
41
+
42
+ const _sensitiveEnvValues = new Set();
43
+ for (const [envKey, val] of Object.entries(process.env)) {
44
+ if (val && val.length >= 8 && SENSITIVE_NAME_PATTERN.test(envKey)) {
45
+ _sensitiveEnvValues.add(val);
46
+ }
47
+ }
48
+ // ─────────────────────────────────────────────────────────────────────────────
49
+
24
50
  class SecretScanner {
25
51
  constructor() {
26
52
  this.detectionCount = 0;
27
53
  }
28
54
 
55
+ /**
56
+ * Add new secret values to the blind redaction set at runtime.
57
+ * Called by AgentLoop when tenant API keys are resolved.
58
+ * @param {string[]} values
59
+ */
60
+ addKnownSecrets(values) {
61
+ for (const v of values) {
62
+ if (v && v.length >= 8) _sensitiveEnvValues.add(v);
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Blind-redact exact env var values from text.
68
+ * Handles secrets that don't match any regex pattern.
69
+ * @param {string} text
70
+ * @returns {{ redacted: string, count: number }}
71
+ */
72
+ _blindRedactEnv(text) {
73
+ let out = text;
74
+ let count = 0;
75
+ for (const val of _sensitiveEnvValues) {
76
+ if (out.includes(val)) {
77
+ // Use split+join instead of replaceAll for safety with special regex chars
78
+ out = out.split(val).join("[REDACTED:ENV_SECRET]");
79
+ count++;
80
+ }
81
+ }
82
+ return { redacted: out, count };
83
+ }
84
+
29
85
  /**
30
86
  * Scan text for secrets and return findings.
31
87
  * @param {string} text - Text to scan
@@ -40,13 +96,11 @@ class SecretScanner {
40
96
  let redacted = text;
41
97
 
42
98
  for (const { name, pattern } of SECRET_PATTERNS) {
43
- // Reset regex state
44
99
  pattern.lastIndex = 0;
45
100
  let match;
46
101
 
47
102
  while ((match = pattern.exec(text)) !== null) {
48
103
  const value = match[0];
49
- // Skip very short matches or common false positives
50
104
  if (value.length < 8) continue;
51
105
 
52
106
  secrets.push({
@@ -55,8 +109,7 @@ class SecretScanner {
55
109
  position: match.index,
56
110
  });
57
111
 
58
- // Redact in output
59
- redacted = redacted.replace(value, `[REDACTED:${name}]`);
112
+ redacted = redacted.split(value).join(`[REDACTED:${name}]`);
60
113
  }
61
114
  }
62
115
 
@@ -68,24 +121,33 @@ class SecretScanner {
68
121
  });
69
122
  }
70
123
 
71
- return {
72
- found: secrets.length > 0,
73
- secrets,
74
- redacted,
75
- };
124
+ return { found: secrets.length > 0, secrets, redacted };
76
125
  }
77
126
 
78
127
  /**
79
128
  * Scan and redact tool output before feeding back to agent.
129
+ * Applies BOTH pattern-based AND blind env-var redaction.
80
130
  */
81
131
  redactOutput(text) {
132
+ // Layer 1: pattern-based
82
133
  const result = this.scan(text);
83
- if (result.found) {
84
- console.log(
85
- ` [SecretScanner] Redacted ${result.secrets.length} secret(s): ${result.secrets.map((s) => s.type).join(", ")}`
86
- );
134
+ let out = result.redacted;
135
+
136
+ // Layer 2: blind env-var values
137
+ const { redacted: blindRedacted, count: blindCount } = this._blindRedactEnv(out);
138
+ out = blindRedacted;
139
+
140
+ const totalFound = result.secrets.length + blindCount;
141
+ if (totalFound > 0) {
142
+ const types = [
143
+ ...result.secrets.map((s) => s.type),
144
+ ...(blindCount > 0 ? [`ENV_SECRET(x${blindCount})`] : []),
145
+ ];
146
+ console.log(` [SecretScanner] Redacted ${totalFound} secret(s): ${types.join(", ")}`);
147
+ this.detectionCount += blindCount;
87
148
  }
88
- return result.redacted;
149
+
150
+ return out;
89
151
  }
90
152
 
91
153
  /**
@@ -5,7 +5,7 @@ import { config } from "../config/default.js";
5
5
  import eventBus from "../core/EventBus.js";
6
6
 
7
7
  /**
8
- * Secret Vault encrypted secret storage.
8
+ * Secret Vault - encrypted secret storage.
9
9
  *
10
10
  * All API keys, tokens, and credentials are encrypted at rest using
11
11
  * AES-256-GCM with a user-provided master passphrase.
@@ -88,7 +88,7 @@ class SecretVault {
88
88
  }
89
89
 
90
90
  /**
91
- * Lock the vault clear decrypted secrets from memory.
91
+ * Lock the vault - clear decrypted secrets from memory.
92
92
  */
93
93
  lock() {
94
94
  this.secrets = null;
@@ -5,7 +5,7 @@ import taskQueue from "../core/TaskQueue.js";
5
5
  import eventBus from "../core/EventBus.js";
6
6
 
7
7
  /**
8
- * Heartbeat periodic proactive check.
8
+ * Heartbeat - periodic proactive check.
9
9
  *
10
10
  * Reads HEARTBEAT.md for user-defined checks.
11
11
  * Every N minutes, creates a task: "Check status per HEARTBEAT.md"
@@ -28,12 +28,12 @@ class Heartbeat {
28
28
  */
29
29
  start() {
30
30
  if (!config.daemonMode) {
31
- console.log(`[Heartbeat] Skipped daemon mode not enabled`);
31
+ console.log(`[Heartbeat] Skipped - daemon mode not enabled`);
32
32
  return;
33
33
  }
34
34
 
35
35
  if (!existsSync(this.heartbeatPath)) {
36
- console.log(`[Heartbeat] No HEARTBEAT.md found heartbeat disabled`);
36
+ console.log(`[Heartbeat] No HEARTBEAT.md found - heartbeat disabled`);
37
37
  return;
38
38
  }
39
39
 
@@ -7,7 +7,7 @@ import taskQueue from "../core/TaskQueue.js";
7
7
  import eventBus from "../core/EventBus.js";
8
8
 
9
9
  /**
10
- * Scheduler cron-based task scheduling.
10
+ * Scheduler - cron-based task scheduling.
11
11
  *
12
12
  * Schedules stored in data/schedules.json.
13
13
  * Each schedule creates a Task when its cron expression triggers.
@@ -22,7 +22,7 @@ class Scheduler {
22
22
  }
23
23
 
24
24
  /**
25
- * Start the scheduler load and activate all schedules.
25
+ * Start the scheduler - load and activate all schedules.
26
26
  */
27
27
  start() {
28
28
  this.loadSchedules();
@@ -35,12 +35,12 @@ class Scheduler {
35
35
 
36
36
  this.running = true;
37
37
  console.log(
38
- `[Scheduler] Started ${this.schedules.size} schedule(s) loaded`
38
+ `[Scheduler] Started - ${this.schedules.size} schedule(s) loaded`
39
39
  );
40
40
  }
41
41
 
42
42
  /**
43
- * Stop the scheduler cancel all cron jobs.
43
+ * Stop the scheduler - cancel all cron jobs.
44
44
  */
45
45
  stop() {
46
46
  for (const [id, job] of this.cronJobs) {
@@ -92,7 +92,7 @@ class Scheduler {
92
92
  }
93
93
 
94
94
  /**
95
- * Update an existing schedule (patch only fields provided are changed).
95
+ * Update an existing schedule (patch - only fields provided are changed).
96
96
  * @param {string} id - Schedule ID (full or prefix)
97
97
  * @param {object} patch - Fields to update: cronExpression, taskInput, name, enabled
98
98
  */
@@ -105,7 +105,7 @@ class Scheduler {
105
105
  const schedule = this.schedules.get(fullId);
106
106
  if (!schedule) throw new Error(`Schedule not found: ${id}`);
107
107
 
108
- // Update cron expression requires restarting the cron job
108
+ // Update cron expression - requires restarting the cron job
109
109
  if (patch.cronExpression && patch.cronExpression !== schedule.cronExpression) {
110
110
  if (!cron.validate(patch.cronExpression)) {
111
111
  throw new Error(`Invalid cron expression: ${patch.cronExpression}`);
@@ -1,110 +1,215 @@
1
1
  import chalk from "chalk";
2
2
 
3
3
  /**
4
- * Daemora CLI Theme — professional color palette and symbols.
5
- * No emojis. Clean Unicode symbols only.
4
+ * Daemora CLI Theme
5
+ * Color palette matches the UI exactly:
6
+ * Cyan #00d9ff — primary brand / headings / CTAs
7
+ * Teal #4ECDC4 — secondary / commands / accents
8
+ * Red #ff4458 — danger / features (matches logo horns)
9
+ * Green #00ff88 — success / security / live badges
10
+ * Amber #ffaa00 — warning / scheduling / [NEW] badges
11
+ * Muted #64748b — slate-500 body text
12
+ * Dim #94a3b8 — slate-400 captions
6
13
  */
7
-
8
- const P = {
9
- brand: "#7C6AFF",
10
- accent: "#4ECDC4",
11
- success: "#2ECC71",
12
- warning: "#F1C40F",
13
- error: "#E74C3C",
14
- muted: "#7F8C8D",
15
- info: "#3498DB",
16
- dim: "#555E68",
14
+ export const P = {
15
+ cyan: "#00d9ff",
16
+ teal: "#4ECDC4",
17
+ red: "#ff4458",
18
+ green: "#00ff88",
19
+ amber: "#ffaa00",
20
+ muted: "#64748b",
21
+ dim: "#94a3b8",
22
+ border: "#1f1f2e",
23
+ // semantic aliases
24
+ get brand() { return this.cyan; },
25
+ get accent() { return this.teal; },
26
+ get success() { return this.green; },
27
+ get warning() { return this.amber; },
28
+ get error() { return this.red; },
17
29
  };
18
30
 
19
31
  export const t = {
20
- brand: (s) => chalk.hex(P.brand)(s),
21
- accent: (s) => chalk.hex(P.accent)(s),
22
- success: (s) => chalk.hex(P.success)(s),
23
- warning: (s) => chalk.hex(P.warning)(s),
24
- error: (s) => chalk.hex(P.error)(s),
32
+ brand: (s) => chalk.hex(P.cyan)(s),
33
+ accent: (s) => chalk.hex(P.teal)(s),
34
+ success: (s) => chalk.hex(P.green)(s),
35
+ warning: (s) => chalk.hex(P.amber)(s),
36
+ error: (s) => chalk.hex(P.red)(s),
25
37
  muted: (s) => chalk.hex(P.muted)(s),
26
- info: (s) => chalk.hex(P.info)(s),
27
38
  dim: (s) => chalk.hex(P.dim)(s),
28
39
  bold: (s) => chalk.bold(s),
29
- h: (s) => chalk.bold.hex(P.brand)(s),
30
- cmd: (s) => chalk.hex(P.accent)(s),
40
+ h: (s) => chalk.bold.hex(P.cyan)(s),
41
+ h2: (s) => chalk.bold.hex(P.teal)(s),
42
+ cmd: (s) => chalk.hex(P.teal)(s),
43
+ new: (s) => chalk.hex(P.amber)(s),
31
44
  };
32
45
 
33
46
  export const S = {
34
- check: chalk.hex(P.success)("\u2714"),
35
- cross: chalk.hex(P.error)("\u2718"),
36
- arrow: chalk.hex(P.brand)("\u25B8"),
37
- dot: chalk.hex(P.muted)("\u00B7"),
38
- bar: chalk.hex(P.dim)("\u2502"),
39
- dash: chalk.hex(P.dim)("\u2500"),
40
- diamond: chalk.hex(P.brand)("\u25C6"),
41
- shield: chalk.hex(P.success)("\u25C8"),
42
- lock: chalk.hex(P.warning)("\u25A3"),
43
- gear: chalk.hex(P.muted)("\u25CB"),
44
- bolt: chalk.hex(P.warning)("\u25CF"),
47
+ check: chalk.hex(P.green)(""),
48
+ cross: chalk.hex(P.red)(""),
49
+ arrow: chalk.hex(P.cyan)(""),
50
+ dot: chalk.hex(P.muted)("·"),
51
+ bar: chalk.hex(P.muted)(""),
52
+ dash: chalk.hex(P.border)(""),
53
+ diamond: chalk.hex(P.cyan)(""),
54
+ shield: chalk.hex(P.green)(""),
55
+ lock: chalk.hex(P.amber)(""),
56
+ gear: chalk.hex(P.muted)(""),
57
+ bolt: chalk.hex(P.cyan)(""),
58
+ eye: chalk.hex(P.red)("◉"),
59
+ star: chalk.hex(P.amber)("★"),
45
60
  };
46
61
 
62
+ // ─── Gradient helpers ──────────────────────────────────────────────────────
63
+
64
+ /** Interpolate a single hex colour component */
65
+ function lerp(a, b, t) { return Math.round(a + (b - a) * t); }
66
+
67
+ /**
68
+ * Render a string with a left→right cyan→teal gradient.
69
+ * One chalk call per visible character — fast enough for a one-shot banner.
70
+ */
71
+ function gradientLine(line) {
72
+ const [cyR, cyG, cyB] = [0x00, 0xd9, 0xff]; // #00d9ff
73
+ const [tlR, tlG, tlB] = [0x4e, 0xcd, 0xc4]; // #4ECDC4
74
+ let out = "";
75
+ const len = line.length;
76
+ for (let i = 0; i < len; i++) {
77
+ const frac = len > 1 ? i / (len - 1) : 0;
78
+ out += chalk.rgb(
79
+ lerp(cyR, tlR, frac),
80
+ lerp(cyG, tlG, frac),
81
+ lerp(cyB, tlB, frac),
82
+ )(line[i]);
83
+ }
84
+ return out;
85
+ }
86
+
87
+ // ─── Banner ────────────────────────────────────────────────────────────────
88
+
89
+ /**
90
+ * DAEMORA ASCII art — figlet "Doom" font.
91
+ * Width: ~65 chars — fits a standard 80-col terminal.
92
+ */
93
+ const DAEMORA_ART = [
94
+ "██████╗ █████╗ ███████╗███╗ ███╗ ██████╗ ██████╗ █████╗ ",
95
+ "██╔══██╗██╔══██╗██╔════╝████╗ ████║██╔═══██╗██╔══██╗██╔══██╗",
96
+ "██║ ██║███████║█████╗ ██╔████╔██║██║ ██║██████╔╝███████║",
97
+ "██║ ██║██╔══██║██╔══╝ ██║╚██╔╝██║██║ ██║██╔══██╗██╔══██║",
98
+ "██████╔╝██║ ██║███████╗██║ ╚═╝ ██║╚██████╔╝██║ ██║██║ ██║",
99
+ "╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝",
100
+ ];
101
+
102
+ /** Logo mark — small demon eye motif drawn with ASCII */
103
+ function logoMark() {
104
+ return [
105
+ chalk.hex(P.red)(" ▲ ▲"),
106
+ chalk.hex(P.red)(" ▲ ▲ ▲ ▲"),
107
+ chalk.hex(P.dim)(" ╔═══════════════════╗"),
108
+ chalk.hex(P.dim)(" ║") +
109
+ chalk.hex(P.muted)(" ") +
110
+ chalk.hex(P.red)("◤▬▬▬▬◥") +
111
+ chalk.hex(P.muted)(" ") +
112
+ chalk.hex(P.red)("◤▬▬▬▬◥") +
113
+ chalk.hex(P.muted)(" ") +
114
+ chalk.hex(P.dim)("║"),
115
+ chalk.hex(P.dim)(" ║") +
116
+ chalk.hex(P.muted)(" ") +
117
+ chalk.hex(P.red)("◉") +
118
+ chalk.hex(P.muted)(" ") +
119
+ chalk.hex(P.red)("◉") +
120
+ chalk.hex(P.muted)(" ") +
121
+ chalk.hex(P.dim)("║"),
122
+ chalk.hex(P.dim)(" ╚═══════════════════╝"),
123
+ chalk.hex(P.cyan)(" ◦ ◦"),
124
+ ].join("\n");
125
+ }
126
+
47
127
  export function banner() {
48
- const w = 56;
49
- const line = chalk.hex(P.brand)("\u2501".repeat(w));
50
- const dimLine = chalk.hex(P.dim)("\u2500".repeat(w));
51
- console.log("");
52
- console.log(line);
128
+ const w = 67;
129
+ const topLine = chalk.hex(P.cyan)("".repeat(w));
130
+ const botLine = chalk.hex(P.teal)("".repeat(w));
131
+
132
+ console.log("\n" + topLine);
53
133
  console.log("");
54
- console.log(chalk.bold.hex(P.brand)(
55
- " \u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588"
56
- ));
57
- console.log(chalk.bold.hex(P.brand)(
58
- " \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588"
59
- ));
60
- console.log(chalk.bold.hex(P.brand)(
61
- " \u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588"
62
- ));
63
- console.log(chalk.bold.hex(P.brand)(
64
- " \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588"
65
- ));
66
- console.log(chalk.bold.hex(P.brand)(
67
- " \u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588"
68
- ));
134
+
135
+ // Gradient DAEMORA lettering
136
+ for (const line of DAEMORA_ART) {
137
+ console.log(" " + gradientLine(line));
138
+ }
139
+
69
140
  console.log("");
70
- console.log(chalk.hex(P.muted)(" Your 24/7 AI Digital Worker"));
71
- console.log(dimLine);
141
+ console.log(
142
+ " " +
143
+ chalk.hex(P.muted)(" ") +
144
+ chalk.hex(P.cyan)("◉") +
145
+ chalk.hex(P.dim)(" Your 24/7 AI Agent ") +
146
+ chalk.hex(P.red)("◉"),
147
+ );
148
+ console.log(botLine);
72
149
  console.log("");
73
150
  }
74
151
 
152
+ // ─── Step header (progress bar) ────────────────────────────────────────────
153
+
75
154
  export function stepHeader(current, total, title) {
76
- const filled = Math.round((current / total) * 20);
77
- const empty = 20 - filled;
78
- const bar = chalk.hex(P.brand)("\u2588".repeat(filled)) + chalk.hex(P.dim)("\u2591".repeat(empty));
79
- const label = chalk.hex(P.dim)(`[${current}/${total}]`);
80
- console.log(`\n ${bar} ${label} ${chalk.bold(title)}\n`);
155
+ const filled = Math.round((current / total) * 22);
156
+ const empty = 22 - filled;
157
+ const bar =
158
+ chalk.hex(P.cyan)("█".repeat(filled)) +
159
+ chalk.hex(P.border)("░".repeat(empty));
160
+ const label = chalk.hex(P.muted)(`[${current}/${total}]`);
161
+ const w = 67;
162
+ const line = chalk.hex(P.border)("─".repeat(w));
163
+ console.log(`\n${line}`);
164
+ console.log(` ${bar} ${label} ${chalk.bold.hex(P.teal)(title)}`);
165
+ console.log(`${line}\n`);
81
166
  }
82
167
 
168
+ // ─── Key/value row ─────────────────────────────────────────────────────────
169
+
83
170
  export function kv(key, value) {
84
171
  console.log(` ${S.bar} ${t.muted(key)} ${value}`);
85
172
  }
86
173
 
174
+ // ─── Summary table ─────────────────────────────────────────────────────────
175
+
87
176
  export function summaryTable(title, rows) {
88
177
  const maxKey = Math.max(...rows.map(([k]) => k.length), 10);
89
- const w = maxKey + 30;
90
- const line = chalk.hex(P.dim)("\u2500".repeat(w));
91
- console.log(`\n ${t.h(title)}`);
92
- console.log(` ${line}`);
178
+ const w = maxKey + 34;
179
+ const topLine = chalk.hex(P.cyan)("".repeat(w));
180
+ const rowLine = chalk.hex(P.border)("─".repeat(w));
181
+ console.log(`\n ${chalk.bold.hex(P.cyan)(title)}`);
182
+ console.log(` ${topLine}`);
93
183
  for (const [key, val] of rows) {
94
184
  const k = t.muted(key.padEnd(maxKey));
95
185
  console.log(` ${S.bar} ${k} ${val}`);
96
186
  }
97
- console.log(` ${line}`);
187
+ console.log(` ${rowLine}`);
98
188
  }
99
189
 
190
+ // ─── Complete banner ────────────────────────────────────────────────────────
191
+
100
192
  export function completeBanner(lines) {
101
- const w = 56;
102
- const line = chalk.hex(P.success)("\u2501".repeat(w));
193
+ const w = 67;
194
+ const line = chalk.hex(P.green)("".repeat(w));
103
195
  console.log(`\n${line}`);
104
- console.log(` ${S.check} ${chalk.bold.hex(P.success)("Setup Complete")}`);
196
+ console.log(` ${S.check} ${chalk.bold.hex(P.green)("Setup Complete")}`);
197
+ console.log(` ${chalk.hex(P.dim)("─".repeat(w - 2))}`);
105
198
  console.log("");
106
199
  for (const l of lines) {
107
200
  console.log(` ${l}`);
108
201
  }
109
202
  console.log(`${line}\n`);
110
203
  }
204
+
205
+ // ─── Section header (for info commands) ────────────────────────────────────
206
+
207
+ export function sectionHeader(title, subtitle = "") {
208
+ const w = 67;
209
+ const topLine = chalk.hex(P.cyan)("━".repeat(w));
210
+ const botLine = chalk.hex(P.border)("─".repeat(w));
211
+ console.log(`\n${topLine}`);
212
+ const sub = subtitle ? ` ${chalk.hex(P.muted)(subtitle)}` : "";
213
+ console.log(` ${chalk.bold.hex(P.cyan)(title)}${sub}`);
214
+ console.log(`${botLine}`);
215
+ }