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/tools/cronTool.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* cron(action, paramsJson?)
|
|
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
|
|
7
|
-
* list
|
|
8
|
-
* add
|
|
9
|
-
* update
|
|
10
|
-
* enable
|
|
11
|
-
* disable
|
|
12
|
-
* remove
|
|
13
|
-
* run
|
|
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)}
|
|
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
|
|
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
|
|
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
|
|
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?)
|
|
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"})`;
|
package/src/tools/editFile.js
CHANGED
|
@@ -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
|
|
5
|
+
* Multi-strategy file editing - inspired by Gemini CLI.
|
|
6
6
|
*
|
|
7
7
|
* Strategy chain:
|
|
8
|
-
* 1. EXACT match
|
|
9
|
-
* 2. FLEXIBLE match
|
|
10
|
-
* 3. If all fail
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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?)
|
|
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
|
|
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
|
|
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?)
|
|
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";
|