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
@@ -1,16 +1,16 @@
1
1
  /**
2
- * cron(action, paramsJson?) Schedule recurring tasks from within the agent.
2
+ * cron(action, paramsJson?) - Schedule recurring tasks from within the agent.
3
3
  * Bridge to the existing Scheduler. Inspired by OpenClaw's cron tool.
4
4
  *
5
5
  * Actions:
6
- * status show scheduler status
7
- * list list all schedules
8
- * add create a new schedule
9
- * update patch an existing schedule (change expression, name, or taskInput)
10
- * enable re-enable a disabled schedule
11
- * disable pause a schedule without deleting it
12
- * remove delete a schedule permanently
13
- * run trigger a schedule immediately (regardless of cron timing)
6
+ * status - show scheduler status
7
+ * list - list all schedules
8
+ * add - create a new schedule
9
+ * update - patch an existing schedule (change expression, name, or taskInput)
10
+ * enable - re-enable a disabled schedule
11
+ * disable - pause a schedule without deleting it
12
+ * remove - delete a schedule permanently
13
+ * run - trigger a schedule immediately (regardless of cron timing)
14
14
  */
15
15
  import scheduler from "../scheduler/Scheduler.js";
16
16
 
@@ -35,14 +35,14 @@ export function cron(action, paramsJson) {
35
35
  return schedules
36
36
  .map(
37
37
  (s) =>
38
- `• ${s.id.slice(0, 8)} "${s.name}" | ${s.cronExpression} | ${s.enabled ? "enabled" : "DISABLED"} | runs: ${s.runCount} | last: ${s.lastRun || "never"}`
38
+ `• ${s.id.slice(0, 8)} - "${s.name}" | ${s.cronExpression} | ${s.enabled ? "enabled" : "DISABLED"} | runs: ${s.runCount} | last: ${s.lastRun || "never"}`
39
39
  )
40
40
  .join("\n");
41
41
  }
42
42
 
43
43
  case "add": {
44
44
  if (!params.cronExpression) return 'Error: cronExpression is required. Example: "0 9 * * *" for daily at 9am.';
45
- if (!params.taskInput) return "Error: taskInput is required the task/message to send when triggered.";
45
+ if (!params.taskInput) return "Error: taskInput is required - the task/message to send when triggered.";
46
46
  const schedule = scheduler.create({
47
47
  cronExpression: params.cronExpression,
48
48
  taskInput: params.taskInput,
@@ -67,13 +67,13 @@ export function cron(action, paramsJson) {
67
67
  case "enable": {
68
68
  if (!params.id) return 'Error: id is required.';
69
69
  const schedule = scheduler.update(params.id, { enabled: true });
70
- return `Schedule "${schedule.name}" (${params.id.slice(0, 8)}) enabled will run on next trigger.`;
70
+ return `Schedule "${schedule.name}" (${params.id.slice(0, 8)}) enabled - will run on next trigger.`;
71
71
  }
72
72
 
73
73
  case "disable": {
74
74
  if (!params.id) return 'Error: id is required.';
75
75
  const schedule = scheduler.update(params.id, { enabled: false });
76
- return `Schedule "${schedule.name}" (${params.id.slice(0, 8)}) disabled cron job paused, not deleted.`;
76
+ return `Schedule "${schedule.name}" (${params.id.slice(0, 8)}) disabled - cron job paused, not deleted.`;
77
77
  }
78
78
 
79
79
  case "remove": {
@@ -97,7 +97,7 @@ export function cron(action, paramsJson) {
97
97
  }
98
98
 
99
99
  export const cronDescription =
100
- 'cron(action, paramsJson?) Manage scheduled recurring tasks. ' +
100
+ 'cron(action, paramsJson?) - Manage scheduled recurring tasks. ' +
101
101
  'Actions: "status", "list", ' +
102
102
  '"add" ({"cronExpression":"0 9 * * *","taskInput":"Check emails","name":"Morning"}), ' +
103
103
  '"update" ({"id":"...","cronExpression":"0 10 * * *"}), ' +
@@ -0,0 +1,165 @@
1
+ /**
2
+ * database - Query databases: SQLite (built-in), PostgreSQL, MySQL.
3
+ * SQLite: uses better-sqlite3 or falls back to built-in node:sqlite (Node 22+).
4
+ * PostgreSQL/MySQL: requires pg / mysql2 package.
5
+ * Security: uses parameterized queries for all user-supplied values.
6
+ */
7
+
8
+ export async function database(action, paramsJson) {
9
+ if (!action) return "Error: action required. Valid: query, execute, schema, list";
10
+ const params = paramsJson
11
+ ? (typeof paramsJson === "string" ? JSON.parse(paramsJson) : paramsJson)
12
+ : {};
13
+
14
+ const { type = "sqlite", dbPath, connectionString, query, values = [], table } = params;
15
+
16
+ // ── SQLite ──────────────────────────────────────────────────────────────
17
+ if (type === "sqlite") {
18
+ if (!dbPath) return "Error: dbPath is required for SQLite";
19
+
20
+ let db;
21
+ try {
22
+ // Try better-sqlite3 first (synchronous, easiest)
23
+ const { default: Database } = await import("better-sqlite3");
24
+ db = new Database(dbPath, { readonly: action === "query" || action === "schema" || action === "list" });
25
+ } catch {
26
+ // Fall back to node:sqlite (Node 22+)
27
+ try {
28
+ const { DatabaseSync } = await import("node:sqlite");
29
+ db = new DatabaseSync(dbPath, { open: true });
30
+ } catch {
31
+ return "Error: SQLite driver not found. Run: npm install better-sqlite3 (or use Node 22+ for built-in sqlite)";
32
+ }
33
+ }
34
+
35
+ try {
36
+ if (action === "query" || action === "execute") {
37
+ if (!query) return "Error: query is required";
38
+ // Detect if it's a select (returns rows) or write (returns changes)
39
+ const isSelect = /^\s*select\b/i.test(query);
40
+ if (isSelect || action === "query") {
41
+ const stmt = db.prepare(query);
42
+ const rows = stmt.all(...values);
43
+ if (!rows.length) return "Query returned 0 rows";
44
+ return JSON.stringify(rows, null, 2);
45
+ } else {
46
+ const stmt = db.prepare(query);
47
+ const info = stmt.run(...values);
48
+ return `OK: ${info.changes} row(s) affected, lastInsertRowid=${info.lastInsertRowid}`;
49
+ }
50
+ }
51
+
52
+ if (action === "schema") {
53
+ const tables = db.prepare("SELECT name, sql FROM sqlite_master WHERE type='table' ORDER BY name").all();
54
+ if (!tables.length) return "No tables found in database";
55
+ return tables.map(t => `-- ${t.name}\n${t.sql}`).join("\n\n");
56
+ }
57
+
58
+ if (action === "list") {
59
+ const tables = db.prepare("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name").all();
60
+ return tables.length ? tables.map(t => t.name).join("\n") : "No tables found";
61
+ }
62
+ } catch (err) {
63
+ return `SQLite error: ${err.message}`;
64
+ } finally {
65
+ if (db?.close) db.close();
66
+ }
67
+ }
68
+
69
+ // ── PostgreSQL ──────────────────────────────────────────────────────────
70
+ if (type === "postgres" || type === "postgresql") {
71
+ const connStr = connectionString || process.env.DATABASE_URL || process.env.POSTGRES_URL;
72
+ if (!connStr) return "Error: connectionString or DATABASE_URL env var required for PostgreSQL";
73
+
74
+ let client;
75
+ try {
76
+ const { Client } = await import("pg");
77
+ client = new Client({ connectionString: connStr });
78
+ await client.connect();
79
+
80
+ if (action === "query" || action === "execute") {
81
+ if (!query) return "Error: query is required";
82
+ const result = await client.query(query, values);
83
+ if (result.rows.length === 0) return `OK: ${result.rowCount} row(s) affected`;
84
+ return JSON.stringify(result.rows, null, 2);
85
+ }
86
+
87
+ if (action === "schema") {
88
+ const tbl = table || "%";
89
+ const result = await client.query(
90
+ "SELECT table_name, column_name, data_type, is_nullable FROM information_schema.columns WHERE table_schema='public' AND table_name LIKE $1 ORDER BY table_name, ordinal_position",
91
+ [tbl]
92
+ );
93
+ if (!result.rows.length) return "No columns found";
94
+ const grouped = {};
95
+ for (const row of result.rows) {
96
+ if (!grouped[row.table_name]) grouped[row.table_name] = [];
97
+ grouped[row.table_name].push(` ${row.column_name} ${row.data_type}${row.is_nullable === "NO" ? " NOT NULL" : ""}`);
98
+ }
99
+ return Object.entries(grouped).map(([t, cols]) => `-- ${t}\n${cols.join("\n")}`).join("\n\n");
100
+ }
101
+
102
+ if (action === "list") {
103
+ const result = await client.query(
104
+ "SELECT table_name FROM information_schema.tables WHERE table_schema='public' ORDER BY table_name"
105
+ );
106
+ return result.rows.map(r => r.table_name).join("\n") || "No tables found";
107
+ }
108
+ } catch (err) {
109
+ return `PostgreSQL error: ${err.message}`;
110
+ } finally {
111
+ if (client) await client.end().catch(() => {});
112
+ }
113
+ }
114
+
115
+ // ── MySQL ───────────────────────────────────────────────────────────────
116
+ if (type === "mysql") {
117
+ const connStr = connectionString || process.env.MYSQL_URL;
118
+ if (!connStr) return "Error: connectionString or MYSQL_URL env var required for MySQL";
119
+
120
+ let conn;
121
+ try {
122
+ const mysql = await import("mysql2/promise");
123
+ conn = await mysql.createConnection(connStr);
124
+
125
+ if (action === "query" || action === "execute") {
126
+ if (!query) return "Error: query is required";
127
+ const [rows] = await conn.execute(query, values);
128
+ if (!Array.isArray(rows)) return `OK: ${rows.affectedRows} row(s) affected`;
129
+ if (!rows.length) return "Query returned 0 rows";
130
+ return JSON.stringify(rows, null, 2);
131
+ }
132
+
133
+ if (action === "schema") {
134
+ const [rows] = await conn.execute("SHOW TABLES");
135
+ return rows.map(r => Object.values(r)[0]).join("\n") || "No tables found";
136
+ }
137
+
138
+ if (action === "list") {
139
+ const [rows] = await conn.execute("SHOW TABLES");
140
+ return rows.map(r => Object.values(r)[0]).join("\n") || "No tables found";
141
+ }
142
+ } catch (err) {
143
+ return `MySQL error: ${err.message}`;
144
+ } finally {
145
+ if (conn) await conn.end().catch(() => {});
146
+ }
147
+ }
148
+
149
+ return `Unknown database type: "${type}". Valid: sqlite, postgres, mysql`;
150
+ }
151
+
152
+ export const databaseDescription =
153
+ `database(action: string, paramsJson?: object) - Query SQLite, PostgreSQL, or MySQL databases.
154
+ action: "query" | "execute" | "schema" | "list"
155
+ params.type: "sqlite" (default) | "postgres" | "mysql"
156
+ sqlite: { dbPath: "/path/to/db.sqlite", query, values? }
157
+ postgres: { connectionString?, query, values? } (or DATABASE_URL env)
158
+ mysql: { connectionString?, query, values? } (or MYSQL_URL env)
159
+ Env vars: DATABASE_URL, POSTGRES_URL, MYSQL_URL
160
+ Note: Uses parameterized queries (values array) to prevent SQL injection.
161
+ Examples:
162
+ database("query", {"dbPath":"./app.db","query":"SELECT * FROM users LIMIT 10"})
163
+ database("execute", {"type":"postgres","query":"INSERT INTO logs(msg) VALUES($1)","values":["hello"]})
164
+ database("schema", {"type":"postgres"})
165
+ database("list", {"dbPath":"./data.db"})`;
@@ -2,12 +2,12 @@ import { readFileSync, writeFileSync } from "node:fs";
2
2
  import filesystemGuard from "../safety/FilesystemGuard.js";
3
3
 
4
4
  /**
5
- * Multi-strategy file editing inspired by Gemini CLI.
5
+ * Multi-strategy file editing - inspired by Gemini CLI.
6
6
  *
7
7
  * Strategy chain:
8
- * 1. EXACT match direct string replacement
9
- * 2. FLEXIBLE match line-by-line, ignoring leading/trailing whitespace
10
- * 3. If all fail show helpful error with nearby context
8
+ * 1. EXACT match - direct string replacement
9
+ * 2. FLEXIBLE match - line-by-line, ignoring leading/trailing whitespace
10
+ * 3. If all fail - show helpful error with nearby context
11
11
  */
12
12
 
13
13
  function exactMatch(content, oldString, newString) {
@@ -74,12 +74,12 @@ function findNearbyContext(content, oldString, maxContext = 3) {
74
74
  }
75
75
 
76
76
  export function editFile(filePath, oldString, newString) {
77
- // Parameter validation model sometimes passes wrong number of args
77
+ // Parameter validation - model sometimes passes wrong number of args
78
78
  if (!filePath || typeof filePath !== "string") {
79
79
  return "Error: editFile requires filePath as the first parameter.";
80
80
  }
81
81
  if (!oldString || typeof oldString !== "string") {
82
- return "Error: editFile requires oldString as the second parameter the text to find and replace.";
82
+ return "Error: editFile requires oldString as the second parameter - the text to find and replace.";
83
83
  }
84
84
  if (newString === undefined || newString === null || typeof newString !== "string") {
85
85
  return "Error: editFile requires 3 parameters: editFile(filePath, oldString, newString). You only passed 2. oldString is the text to FIND in the file, newString is what to REPLACE it with. If you want to append content, use writeFile instead to rewrite the full file, or use editFile with an existing line as oldString and provide the replacement that includes the new content.";
@@ -103,7 +103,7 @@ export function editFile(filePath, oldString, newString) {
103
103
  let result = exactMatch(content, oldString, newString);
104
104
  if (result) {
105
105
  writeFileSync(filePath, result.updated, { encoding: "utf-8" });
106
- console.log(` [editFile] Done ${result.strategy} match, replaced ${result.occurrences} occurrence(s)`);
106
+ console.log(` [editFile] Done - ${result.strategy} match, replaced ${result.occurrences} occurrence(s)`);
107
107
  return `File ${filePath} edited successfully (${result.strategy} match). Replaced ${result.occurrences} occurrence(s).`;
108
108
  }
109
109
 
@@ -111,11 +111,11 @@ export function editFile(filePath, oldString, newString) {
111
111
  result = flexibleMatch(content, oldString, newString);
112
112
  if (result) {
113
113
  writeFileSync(filePath, result.updated, { encoding: "utf-8" });
114
- console.log(` [editFile] Done ${result.strategy} match`);
114
+ console.log(` [editFile] Done - ${result.strategy} match`);
115
115
  return `File ${filePath} edited successfully (${result.strategy} match). Replaced ${result.occurrences} occurrence(s).`;
116
116
  }
117
117
 
118
- // All strategies failed provide helpful error
118
+ // All strategies failed - provide helpful error
119
119
  const nearby = findNearbyContext(content, oldString);
120
120
  let errorMsg = `Error: Could not find the string to replace in ${filePath}.\n`;
121
121
  errorMsg += `Make sure oldString matches the file content (including whitespace/indentation).\n`;
@@ -127,7 +127,7 @@ export function editFile(filePath, oldString, newString) {
127
127
  }
128
128
  }
129
129
 
130
- console.log(` [editFile] Failed no match found`);
130
+ console.log(` [editFile] Failed - no match found`);
131
131
  return errorMsg;
132
132
  } catch (error) {
133
133
  console.log(` [editFile] Failed: ${error.message}`);
@@ -1,5 +1,5 @@
1
1
  /**
2
- * executeCommand(cmd, optionsJson?) Execute a shell command with advanced options.
2
+ * executeCommand(cmd, optionsJson?) - Execute a shell command with advanced options.
3
3
  * Upgraded from 23-line basic version to support: cwd, timeout, env, background mode, stderr.
4
4
  *
5
5
  * Filesystem scoping:
@@ -12,6 +12,7 @@ import { existsSync } from "node:fs";
12
12
  import { resolve } from "node:path";
13
13
  import { config } from "../config/default.js";
14
14
  import filesystemGuard from "../safety/FilesystemGuard.js";
15
+ import { checkCommand } from "../safety/CommandGuard.js";
15
16
 
16
17
  const DEFAULT_TIMEOUT_MS = 120_000; // 2 minutes default
17
18
  const MAX_TIMEOUT_MS = 600_000; // 10 minutes hard max
@@ -36,6 +37,13 @@ export function executeCommand(cmd, optionsJson) {
36
37
  cwd = resolved;
37
38
  }
38
39
 
40
+ // ── Command security guard (always runs, regardless of filesystem config) ──
41
+ const cmdCheck = checkCommand(cmd);
42
+ if (!cmdCheck.allowed) {
43
+ return `Command blocked by security policy: ${cmdCheck.reason}`;
44
+ }
45
+ // ──────────────────────────────────────────────────────────────────────────
46
+
39
47
  // ── Filesystem scope enforcement ───────────────────────────────────────────
40
48
  const allowedPaths = config.filesystem?.allowedPaths || [];
41
49
  if (allowedPaths.length > 0) {
@@ -72,7 +80,7 @@ export function executeCommand(cmd, optionsJson) {
72
80
 
73
81
  console.log(` [executeCommand] Running: ${cmd}${cwdRaw ? ` (cwd: ${cwdRaw})` : ""}${background ? " [background]" : ""}`);
74
82
 
75
- // Background mode spawn detached, return PID immediately
83
+ // Background mode - spawn detached, return PID immediately
76
84
  if (background) {
77
85
  try {
78
86
  const child = spawn("sh", ["-c", cmd], {
@@ -88,7 +96,7 @@ export function executeCommand(cmd, optionsJson) {
88
96
  }
89
97
  }
90
98
 
91
- // Foreground mode wait for result
99
+ // Foreground mode - wait for result
92
100
  try {
93
101
  const result = execSync(cmd, {
94
102
  encoding: "utf-8",
@@ -0,0 +1,79 @@
1
+ /**
2
+ * generateImage - Generate images using OpenAI DALL-E 3 or DALL-E 2.
3
+ * Saves the image to /tmp and returns the file path for the agent to use.
4
+ */
5
+ import { writeFileSync, mkdirSync } from "node:fs";
6
+ import { join } from "node:path";
7
+ import { tmpdir } from "node:os";
8
+ import tenantContext from "../tenants/TenantContext.js";
9
+
10
+ export async function generateImage(prompt, optionsJson) {
11
+ if (!prompt) return "Error: prompt is required.";
12
+
13
+ let opts = {};
14
+ if (optionsJson) { try { opts = JSON.parse(optionsJson); } catch { return "Error: optionsJson must be valid JSON."; } }
15
+
16
+ const {
17
+ model = "dall-e-3",
18
+ size = "1024x1024",
19
+ quality = "standard",
20
+ style = "vivid",
21
+ n = 1,
22
+ outputPath = null,
23
+ } = opts;
24
+
25
+ const store = tenantContext.getStore();
26
+ const apiKeys = store?.apiKeys || {};
27
+ const apiKey = apiKeys.OPENAI_API_KEY || process.env.OPENAI_API_KEY;
28
+
29
+ if (!apiKey) return "Error: OPENAI_API_KEY not configured.";
30
+
31
+ const body = { model, prompt, n: Math.min(n, model === "dall-e-3" ? 1 : 10), size, response_format: "b64_json" };
32
+ if (model === "dall-e-3") {
33
+ body.quality = quality;
34
+ body.style = style;
35
+ }
36
+
37
+ try {
38
+ const res = await fetch("https://api.openai.com/v1/images/generations", {
39
+ method: "POST",
40
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
41
+ body: JSON.stringify(body),
42
+ });
43
+
44
+ const data = await res.json();
45
+ if (!res.ok) return `Error: ${data.error?.message || res.status}`;
46
+
47
+ const images = data.data || [];
48
+ if (images.length === 0) return "Error: No images returned.";
49
+
50
+ const saved = [];
51
+ const dir = join(tmpdir(), "daemora-images");
52
+ mkdirSync(dir, { recursive: true });
53
+
54
+ for (let i = 0; i < images.length; i++) {
55
+ const b64 = images[i].b64_json;
56
+ const revised = images[i].revised_prompt || prompt;
57
+ const filePath = outputPath || join(dir, `image-${Date.now()}-${i}.png`);
58
+ writeFileSync(filePath, Buffer.from(b64, "base64"));
59
+ saved.push({ path: filePath, revisedPrompt: revised });
60
+ }
61
+
62
+ const lines = saved.map(s =>
63
+ `Saved: ${s.path}${s.revisedPrompt !== prompt ? `\nRevised prompt: ${s.revisedPrompt}` : ""}`
64
+ );
65
+ return `Generated ${saved.length} image(s):\n${lines.join("\n")}`;
66
+ } catch (err) {
67
+ return `Error generating image: ${err.message}`;
68
+ }
69
+ }
70
+
71
+ export const generateImageDescription =
72
+ `generateImage(prompt: string, optionsJson?: string) - Generate images with DALL-E.
73
+ prompt: description of the image to generate
74
+ optionsJson: {"model":"dall-e-3","size":"1024x1024","quality":"standard","style":"vivid","n":1,"outputPath":"/tmp/out.png"}
75
+ model: "dall-e-3" (default, best quality) or "dall-e-2"
76
+ size: "1024x1024" | "1792x1024" | "1024x1792" (dall-e-3) or "256x256"|"512x512"|"1024x1024" (dall-e-2)
77
+ quality: "standard" | "hd" (dall-e-3 only)
78
+ style: "vivid" | "natural" (dall-e-3 only)
79
+ Returns the file path of the saved image.`;
@@ -0,0 +1,141 @@
1
+ /**
2
+ * gitTool - Git operations: clone, status, diff, log, commit, push, pull, branch, checkout, stash.
3
+ */
4
+ import { execSync } from "node:child_process";
5
+ import { existsSync } from "node:fs";
6
+ import { resolve } from "node:path";
7
+ import filesystemGuard from "../safety/FilesystemGuard.js";
8
+
9
+ const MAX_OUTPUT = 8000;
10
+
11
+ function run(cmd, cwd) {
12
+ return execSync(cmd, { encoding: "utf-8", cwd, timeout: 60000, maxBuffer: 10 * 1024 * 1024, stdio: ["pipe", "pipe", "pipe"] }).trim();
13
+ }
14
+
15
+ export async function gitTool(action, paramsJson) {
16
+ if (!action) return 'Error: action required. Valid: clone, status, diff, log, commit, push, pull, branch, checkout, stash, add, reset, remote';
17
+
18
+ const params = paramsJson ? (typeof paramsJson === "string" ? JSON.parse(paramsJson) : paramsJson) : {};
19
+
20
+ const repoPath = params.path ? resolve(params.path) : process.cwd();
21
+
22
+ if (action !== "clone") {
23
+ const guard = filesystemGuard.checkRead(repoPath);
24
+ if (!guard.allowed) return `Access denied: ${guard.reason}`;
25
+ }
26
+
27
+ try {
28
+ switch (action) {
29
+
30
+ case "clone": {
31
+ const { url, dest, branch } = params;
32
+ if (!url) return "Error: url is required for clone.";
33
+ if (!dest) return "Error: dest is required for clone.";
34
+ const destResolved = resolve(dest);
35
+ const parentGuard = filesystemGuard.checkWrite(destResolved);
36
+ if (!parentGuard.allowed) return `Access denied: ${parentGuard.reason}`;
37
+ const branchFlag = branch ? `-b ${branch}` : "";
38
+ return run(`git clone ${branchFlag} "${url}" "${destResolved}"`);
39
+ }
40
+
41
+ case "status":
42
+ return run(`git status`, repoPath);
43
+
44
+ case "diff": {
45
+ const { staged = false, file = "" } = params;
46
+ const flag = staged ? "--staged" : "";
47
+ const out = run(`git diff ${flag} ${file}`.trim(), repoPath);
48
+ return out.slice(0, MAX_OUTPUT) || "(no changes)";
49
+ }
50
+
51
+ case "log": {
52
+ const { n = 20, oneline = true } = params;
53
+ const fmt = oneline ? "--oneline" : `--pretty=format:"%h %an %ar - %s"`;
54
+ return run(`git log ${fmt} -n ${n}`, repoPath);
55
+ }
56
+
57
+ case "add": {
58
+ const { files = "." } = params;
59
+ const fileList = Array.isArray(files) ? files.join(" ") : files;
60
+ run(`git add ${fileList}`, repoPath);
61
+ return `Staged: ${fileList}`;
62
+ }
63
+
64
+ case "commit": {
65
+ const { message, all = false } = params;
66
+ if (!message) return "Error: message is required for commit.";
67
+ const allFlag = all ? "-a" : "";
68
+ return run(`git commit ${allFlag} -m ${JSON.stringify(message)}`, repoPath);
69
+ }
70
+
71
+ case "push": {
72
+ const { remote = "origin", branch: br = "", force = false } = params;
73
+ const forceFlag = force ? "--force-with-lease" : "";
74
+ return run(`git push ${forceFlag} ${remote} ${br}`.trim(), repoPath);
75
+ }
76
+
77
+ case "pull": {
78
+ const { remote = "origin", branch: br = "", rebase = false } = params;
79
+ const rebaseFlag = rebase ? "--rebase" : "";
80
+ return run(`git pull ${rebaseFlag} ${remote} ${br}`.trim(), repoPath);
81
+ }
82
+
83
+ case "branch": {
84
+ const { name, delete: del, list = false } = params;
85
+ if (list || !name) return run(`git branch -a`, repoPath);
86
+ if (del) return run(`git branch -d "${name}"`, repoPath);
87
+ return run(`git branch "${name}"`, repoPath);
88
+ }
89
+
90
+ case "checkout": {
91
+ const { branch: br, file, create = false } = params;
92
+ if (file) return run(`git checkout -- "${file}"`, repoPath);
93
+ if (!br) return "Error: branch is required for checkout.";
94
+ const createFlag = create ? "-b" : "";
95
+ return run(`git checkout ${createFlag} "${br}"`, repoPath);
96
+ }
97
+
98
+ case "stash": {
99
+ const { sub = "push", message: msg = "" } = params;
100
+ if (sub === "push") return run(`git stash push ${msg ? `-m ${JSON.stringify(msg)}` : ""}`, repoPath);
101
+ if (sub === "pop") return run(`git stash pop`, repoPath);
102
+ if (sub === "list") return run(`git stash list`, repoPath);
103
+ if (sub === "drop") return run(`git stash drop`, repoPath);
104
+ return `Unknown stash subcommand: ${sub}`;
105
+ }
106
+
107
+ case "reset": {
108
+ const { hard = false, file } = params;
109
+ if (file) return run(`git reset HEAD "${file}"`, repoPath);
110
+ return run(`git reset ${hard ? "--hard" : "--soft"} HEAD~1`, repoPath);
111
+ }
112
+
113
+ case "remote":
114
+ return run(`git remote -v`, repoPath);
115
+
116
+ default:
117
+ return `Unknown action: "${action}". Valid: clone, status, diff, log, add, commit, push, pull, branch, checkout, stash, reset, remote`;
118
+ }
119
+ } catch (err) {
120
+ const stderr = err.stderr?.toString().trim() || "";
121
+ const stdout = err.stdout?.toString().trim() || "";
122
+ return `Git error: ${stderr || stdout || err.message}`;
123
+ }
124
+ }
125
+
126
+ export const gitToolDescription =
127
+ `gitTool(action: string, paramsJson?: string) - Git operations on a repository.
128
+ Actions:
129
+ clone - {"url":"https://github.com/...","dest":"./myrepo","branch":"main"}
130
+ status - {"path":"./myrepo"}
131
+ diff - {"path":"./myrepo","staged":false,"file":"src/index.js"}
132
+ log - {"path":"./myrepo","n":20,"oneline":true}
133
+ add - {"path":"./myrepo","files":["src/index.js","README.md"]}
134
+ commit - {"path":"./myrepo","message":"fix: bug in auth","all":false}
135
+ push - {"path":"./myrepo","remote":"origin","branch":"main","force":false}
136
+ pull - {"path":"./myrepo","remote":"origin","rebase":false}
137
+ branch - {"path":"./myrepo","name":"feature/x"} or {"list":true}
138
+ checkout - {"path":"./myrepo","branch":"main"} or {"create":true,"branch":"feature/x"}
139
+ stash - {"path":"./myrepo","sub":"push|pop|list|drop","message":"wip"}
140
+ reset - {"path":"./myrepo","hard":false}
141
+ remote - {"path":"./myrepo"}`;
package/src/tools/glob.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * glob(pattern, directory?) Pattern-based file search with modification time sorting.
2
+ * glob(pattern, directory?) - Pattern-based file search with modification time sorting.
3
3
  * Inspired by Claude Code's Glob tool and Gemini CLI's FindFiles.
4
4
  */
5
5
  import { glob as globFn } from "glob";