@madh-io/alfred-ai 0.2.0 → 0.3.1
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/bundle/index.js +1142 -107
- package/package.json +14 -12
package/bundle/index.js
CHANGED
|
@@ -11,7 +11,7 @@ var __export = (target, all) => {
|
|
|
11
11
|
|
|
12
12
|
// ../config/dist/schema.js
|
|
13
13
|
import { z } from "zod";
|
|
14
|
-
var TelegramConfigSchema, DiscordConfigSchema, WhatsAppConfigSchema, MatrixConfigSchema, SignalConfigSchema, StorageConfigSchema, LoggerConfigSchema, SecurityConfigSchema, LLMProviderConfigSchema, AlfredConfigSchema;
|
|
14
|
+
var TelegramConfigSchema, DiscordConfigSchema, WhatsAppConfigSchema, MatrixConfigSchema, SignalConfigSchema, StorageConfigSchema, LoggerConfigSchema, SecurityConfigSchema, LLMProviderConfigSchema, SearchConfigSchema, EmailConfigSchema, AlfredConfigSchema;
|
|
15
15
|
var init_schema = __esm({
|
|
16
16
|
"../config/dist/schema.js"() {
|
|
17
17
|
"use strict";
|
|
@@ -59,6 +59,27 @@ var init_schema = __esm({
|
|
|
59
59
|
temperature: z.number().optional(),
|
|
60
60
|
maxTokens: z.number().optional()
|
|
61
61
|
});
|
|
62
|
+
SearchConfigSchema = z.object({
|
|
63
|
+
provider: z.enum(["brave", "searxng", "tavily", "duckduckgo"]),
|
|
64
|
+
apiKey: z.string().optional(),
|
|
65
|
+
baseUrl: z.string().optional()
|
|
66
|
+
});
|
|
67
|
+
EmailConfigSchema = z.object({
|
|
68
|
+
imap: z.object({
|
|
69
|
+
host: z.string(),
|
|
70
|
+
port: z.number(),
|
|
71
|
+
secure: z.boolean()
|
|
72
|
+
}),
|
|
73
|
+
smtp: z.object({
|
|
74
|
+
host: z.string(),
|
|
75
|
+
port: z.number(),
|
|
76
|
+
secure: z.boolean()
|
|
77
|
+
}),
|
|
78
|
+
auth: z.object({
|
|
79
|
+
user: z.string(),
|
|
80
|
+
pass: z.string()
|
|
81
|
+
})
|
|
82
|
+
});
|
|
62
83
|
AlfredConfigSchema = z.object({
|
|
63
84
|
name: z.string(),
|
|
64
85
|
telegram: TelegramConfigSchema,
|
|
@@ -69,7 +90,9 @@ var init_schema = __esm({
|
|
|
69
90
|
llm: LLMProviderConfigSchema,
|
|
70
91
|
storage: StorageConfigSchema,
|
|
71
92
|
logger: LoggerConfigSchema,
|
|
72
|
-
security: SecurityConfigSchema
|
|
93
|
+
security: SecurityConfigSchema,
|
|
94
|
+
search: SearchConfigSchema.optional(),
|
|
95
|
+
email: EmailConfigSchema.optional()
|
|
73
96
|
});
|
|
74
97
|
}
|
|
75
98
|
});
|
|
@@ -184,7 +207,12 @@ var init_loader = __esm({
|
|
|
184
207
|
ALFRED_LLM_BASE_URL: ["llm", "baseUrl"],
|
|
185
208
|
ALFRED_STORAGE_PATH: ["storage", "path"],
|
|
186
209
|
ALFRED_LOG_LEVEL: ["logger", "level"],
|
|
187
|
-
ALFRED_OWNER_USER_ID: ["security", "ownerUserId"]
|
|
210
|
+
ALFRED_OWNER_USER_ID: ["security", "ownerUserId"],
|
|
211
|
+
ALFRED_SEARCH_PROVIDER: ["search", "provider"],
|
|
212
|
+
ALFRED_SEARCH_API_KEY: ["search", "apiKey"],
|
|
213
|
+
ALFRED_SEARCH_BASE_URL: ["search", "baseUrl"],
|
|
214
|
+
ALFRED_EMAIL_USER: ["email", "auth", "user"],
|
|
215
|
+
ALFRED_EMAIL_PASS: ["email", "auth", "pass"]
|
|
188
216
|
};
|
|
189
217
|
ConfigLoader = class {
|
|
190
218
|
loadConfig(configPath) {
|
|
@@ -255,6 +283,131 @@ var init_dist2 = __esm({
|
|
|
255
283
|
}
|
|
256
284
|
});
|
|
257
285
|
|
|
286
|
+
// ../storage/dist/migrations/migrator.js
|
|
287
|
+
var Migrator;
|
|
288
|
+
var init_migrator = __esm({
|
|
289
|
+
"../storage/dist/migrations/migrator.js"() {
|
|
290
|
+
"use strict";
|
|
291
|
+
Migrator = class {
|
|
292
|
+
db;
|
|
293
|
+
constructor(db) {
|
|
294
|
+
this.db = db;
|
|
295
|
+
this.ensureMigrationsTable();
|
|
296
|
+
}
|
|
297
|
+
ensureMigrationsTable() {
|
|
298
|
+
this.db.exec(`
|
|
299
|
+
CREATE TABLE IF NOT EXISTS _migrations (
|
|
300
|
+
version INTEGER PRIMARY KEY,
|
|
301
|
+
description TEXT,
|
|
302
|
+
applied_at TEXT NOT NULL
|
|
303
|
+
)
|
|
304
|
+
`);
|
|
305
|
+
}
|
|
306
|
+
/** Get current schema version */
|
|
307
|
+
getCurrentVersion() {
|
|
308
|
+
const row = this.db.prepare("SELECT MAX(version) as version FROM _migrations").get();
|
|
309
|
+
return row?.version ?? 0;
|
|
310
|
+
}
|
|
311
|
+
/** Run all pending migrations */
|
|
312
|
+
migrate(migrations) {
|
|
313
|
+
const sorted = [...migrations].sort((a, b) => a.version - b.version);
|
|
314
|
+
const currentVersion = this.getCurrentVersion();
|
|
315
|
+
for (const migration of sorted) {
|
|
316
|
+
if (migration.version <= currentVersion) {
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
const run = this.db.transaction(() => {
|
|
320
|
+
migration.up(this.db);
|
|
321
|
+
this.db.prepare("INSERT INTO _migrations (version, description, applied_at) VALUES (?, ?, ?)").run(migration.version, migration.description, (/* @__PURE__ */ new Date()).toISOString());
|
|
322
|
+
});
|
|
323
|
+
run();
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
/** Get list of applied migrations */
|
|
327
|
+
getAppliedMigrations() {
|
|
328
|
+
const rows = this.db.prepare("SELECT version, applied_at FROM _migrations ORDER BY version ASC").all();
|
|
329
|
+
return rows.map((row) => ({
|
|
330
|
+
version: row.version,
|
|
331
|
+
appliedAt: row.applied_at
|
|
332
|
+
}));
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
// ../storage/dist/migrations/index.js
|
|
339
|
+
var MIGRATIONS;
|
|
340
|
+
var init_migrations = __esm({
|
|
341
|
+
"../storage/dist/migrations/index.js"() {
|
|
342
|
+
"use strict";
|
|
343
|
+
init_migrator();
|
|
344
|
+
MIGRATIONS = [
|
|
345
|
+
{
|
|
346
|
+
version: 1,
|
|
347
|
+
description: "Initial schema \u2014 conversations, messages, users, audit_log",
|
|
348
|
+
up(_db) {
|
|
349
|
+
}
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
version: 2,
|
|
353
|
+
description: "Add plugin_skills table for tracking loaded external plugins",
|
|
354
|
+
up(db) {
|
|
355
|
+
db.exec(`
|
|
356
|
+
CREATE TABLE IF NOT EXISTS plugin_skills (
|
|
357
|
+
name TEXT PRIMARY KEY,
|
|
358
|
+
file_path TEXT NOT NULL,
|
|
359
|
+
version TEXT NOT NULL,
|
|
360
|
+
loaded_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
361
|
+
enabled INTEGER NOT NULL DEFAULT 1
|
|
362
|
+
)
|
|
363
|
+
`);
|
|
364
|
+
}
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
version: 3,
|
|
368
|
+
description: "Add memories and reminders tables",
|
|
369
|
+
up(db) {
|
|
370
|
+
db.exec(`
|
|
371
|
+
CREATE TABLE IF NOT EXISTS memories (
|
|
372
|
+
id TEXT PRIMARY KEY,
|
|
373
|
+
user_id TEXT NOT NULL,
|
|
374
|
+
key TEXT NOT NULL,
|
|
375
|
+
value TEXT NOT NULL,
|
|
376
|
+
category TEXT NOT NULL DEFAULT 'general',
|
|
377
|
+
created_at TEXT NOT NULL,
|
|
378
|
+
updated_at TEXT NOT NULL,
|
|
379
|
+
UNIQUE(user_id, key)
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
CREATE INDEX IF NOT EXISTS idx_memories_user
|
|
383
|
+
ON memories(user_id, updated_at DESC);
|
|
384
|
+
|
|
385
|
+
CREATE INDEX IF NOT EXISTS idx_memories_user_category
|
|
386
|
+
ON memories(user_id, category);
|
|
387
|
+
|
|
388
|
+
CREATE TABLE IF NOT EXISTS reminders (
|
|
389
|
+
id TEXT PRIMARY KEY,
|
|
390
|
+
user_id TEXT NOT NULL,
|
|
391
|
+
platform TEXT NOT NULL,
|
|
392
|
+
chat_id TEXT NOT NULL,
|
|
393
|
+
message TEXT NOT NULL,
|
|
394
|
+
trigger_at TEXT NOT NULL,
|
|
395
|
+
created_at TEXT NOT NULL,
|
|
396
|
+
fired INTEGER NOT NULL DEFAULT 0
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
CREATE INDEX IF NOT EXISTS idx_reminders_due
|
|
400
|
+
ON reminders(fired, trigger_at);
|
|
401
|
+
|
|
402
|
+
CREATE INDEX IF NOT EXISTS idx_reminders_user
|
|
403
|
+
ON reminders(user_id, fired);
|
|
404
|
+
`);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
];
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
|
|
258
411
|
// ../storage/dist/database.js
|
|
259
412
|
import BetterSqlite3 from "better-sqlite3";
|
|
260
413
|
import fs2 from "node:fs";
|
|
@@ -263,6 +416,8 @@ var Database;
|
|
|
263
416
|
var init_database = __esm({
|
|
264
417
|
"../storage/dist/database.js"() {
|
|
265
418
|
"use strict";
|
|
419
|
+
init_migrator();
|
|
420
|
+
init_migrations();
|
|
266
421
|
Database = class {
|
|
267
422
|
db;
|
|
268
423
|
constructor(dbPath) {
|
|
@@ -271,6 +426,7 @@ var init_database = __esm({
|
|
|
271
426
|
this.db = new BetterSqlite3(dbPath);
|
|
272
427
|
this.db.pragma("journal_mode = WAL");
|
|
273
428
|
this.initTables();
|
|
429
|
+
this.runMigrations();
|
|
274
430
|
}
|
|
275
431
|
initTables() {
|
|
276
432
|
this.db.exec(`
|
|
@@ -325,6 +481,10 @@ var init_database = __esm({
|
|
|
325
481
|
ON users(platform, platform_user_id);
|
|
326
482
|
`);
|
|
327
483
|
}
|
|
484
|
+
runMigrations() {
|
|
485
|
+
const migrator = new Migrator(this.db);
|
|
486
|
+
migrator.migrate(MIGRATIONS);
|
|
487
|
+
}
|
|
328
488
|
getDb() {
|
|
329
489
|
return this.db;
|
|
330
490
|
}
|
|
@@ -630,21 +790,6 @@ var init_memory_repository = __esm({
|
|
|
630
790
|
}
|
|
631
791
|
});
|
|
632
792
|
|
|
633
|
-
// ../storage/dist/migrations/migrator.js
|
|
634
|
-
var init_migrator = __esm({
|
|
635
|
-
"../storage/dist/migrations/migrator.js"() {
|
|
636
|
-
"use strict";
|
|
637
|
-
}
|
|
638
|
-
});
|
|
639
|
-
|
|
640
|
-
// ../storage/dist/migrations/index.js
|
|
641
|
-
var init_migrations = __esm({
|
|
642
|
-
"../storage/dist/migrations/index.js"() {
|
|
643
|
-
"use strict";
|
|
644
|
-
init_migrator();
|
|
645
|
-
}
|
|
646
|
-
});
|
|
647
|
-
|
|
648
793
|
// ../storage/dist/repositories/reminder-repository.js
|
|
649
794
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
650
795
|
var ReminderRepository;
|
|
@@ -721,15 +866,56 @@ var init_dist3 = __esm({
|
|
|
721
866
|
});
|
|
722
867
|
|
|
723
868
|
// ../llm/dist/provider.js
|
|
724
|
-
|
|
869
|
+
function lookupContextWindow(model) {
|
|
870
|
+
if (KNOWN_CONTEXT_WINDOWS[model])
|
|
871
|
+
return KNOWN_CONTEXT_WINDOWS[model];
|
|
872
|
+
for (const [key, value] of Object.entries(KNOWN_CONTEXT_WINDOWS)) {
|
|
873
|
+
if (model.startsWith(key))
|
|
874
|
+
return value;
|
|
875
|
+
}
|
|
876
|
+
return void 0;
|
|
877
|
+
}
|
|
878
|
+
var KNOWN_CONTEXT_WINDOWS, DEFAULT_CONTEXT_WINDOW, LLMProvider;
|
|
725
879
|
var init_provider = __esm({
|
|
726
880
|
"../llm/dist/provider.js"() {
|
|
727
881
|
"use strict";
|
|
882
|
+
KNOWN_CONTEXT_WINDOWS = {
|
|
883
|
+
// Anthropic
|
|
884
|
+
"claude-opus-4-20250514": { maxInputTokens: 2e5, maxOutputTokens: 32e3 },
|
|
885
|
+
"claude-sonnet-4-20250514": { maxInputTokens: 2e5, maxOutputTokens: 16e3 },
|
|
886
|
+
"claude-haiku-3-5-20241022": { maxInputTokens: 2e5, maxOutputTokens: 8192 },
|
|
887
|
+
// OpenAI
|
|
888
|
+
"gpt-4o": { maxInputTokens: 128e3, maxOutputTokens: 16384 },
|
|
889
|
+
"gpt-4o-mini": { maxInputTokens: 128e3, maxOutputTokens: 16384 },
|
|
890
|
+
"gpt-4-turbo": { maxInputTokens: 128e3, maxOutputTokens: 4096 },
|
|
891
|
+
"gpt-4": { maxInputTokens: 8192, maxOutputTokens: 4096 },
|
|
892
|
+
"gpt-3.5-turbo": { maxInputTokens: 16384, maxOutputTokens: 4096 },
|
|
893
|
+
"o1": { maxInputTokens: 2e5, maxOutputTokens: 1e5 },
|
|
894
|
+
"o1-mini": { maxInputTokens: 128e3, maxOutputTokens: 65536 },
|
|
895
|
+
"o3-mini": { maxInputTokens: 2e5, maxOutputTokens: 1e5 },
|
|
896
|
+
// Common Ollama models
|
|
897
|
+
"llama3.2": { maxInputTokens: 128e3, maxOutputTokens: 4096 },
|
|
898
|
+
"llama3.1": { maxInputTokens: 128e3, maxOutputTokens: 4096 },
|
|
899
|
+
"llama3": { maxInputTokens: 8192, maxOutputTokens: 4096 },
|
|
900
|
+
"mistral": { maxInputTokens: 32e3, maxOutputTokens: 4096 },
|
|
901
|
+
"mistral-small": { maxInputTokens: 32e3, maxOutputTokens: 4096 },
|
|
902
|
+
"mixtral": { maxInputTokens: 32e3, maxOutputTokens: 4096 },
|
|
903
|
+
"gemma2": { maxInputTokens: 8192, maxOutputTokens: 4096 },
|
|
904
|
+
"qwen2.5": { maxInputTokens: 128e3, maxOutputTokens: 4096 },
|
|
905
|
+
"phi3": { maxInputTokens: 128e3, maxOutputTokens: 4096 },
|
|
906
|
+
"deepseek-r1": { maxInputTokens: 128e3, maxOutputTokens: 8192 },
|
|
907
|
+
"command-r": { maxInputTokens: 128e3, maxOutputTokens: 4096 }
|
|
908
|
+
};
|
|
909
|
+
DEFAULT_CONTEXT_WINDOW = { maxInputTokens: 8192, maxOutputTokens: 4096 };
|
|
728
910
|
LLMProvider = class {
|
|
729
911
|
config;
|
|
912
|
+
contextWindow = DEFAULT_CONTEXT_WINDOW;
|
|
730
913
|
constructor(config) {
|
|
731
914
|
this.config = config;
|
|
732
915
|
}
|
|
916
|
+
getContextWindow() {
|
|
917
|
+
return this.contextWindow;
|
|
918
|
+
}
|
|
733
919
|
};
|
|
734
920
|
}
|
|
735
921
|
});
|
|
@@ -748,6 +934,9 @@ var init_anthropic = __esm({
|
|
|
748
934
|
}
|
|
749
935
|
async initialize() {
|
|
750
936
|
this.client = new Anthropic({ apiKey: this.config.apiKey });
|
|
937
|
+
const cw = lookupContextWindow(this.config.model);
|
|
938
|
+
if (cw)
|
|
939
|
+
this.contextWindow = cw;
|
|
751
940
|
}
|
|
752
941
|
async complete(request) {
|
|
753
942
|
const messages = this.mapMessages(request.messages);
|
|
@@ -886,6 +1075,9 @@ var init_openai = __esm({
|
|
|
886
1075
|
apiKey: this.config.apiKey,
|
|
887
1076
|
baseURL: this.config.baseUrl
|
|
888
1077
|
});
|
|
1078
|
+
const cw = lookupContextWindow(this.config.model);
|
|
1079
|
+
if (cw)
|
|
1080
|
+
this.contextWindow = cw;
|
|
889
1081
|
}
|
|
890
1082
|
async complete(request) {
|
|
891
1083
|
const messages = this.mapMessages(request.messages, request.system);
|
|
@@ -1132,6 +1324,34 @@ var init_ollama = __esm({
|
|
|
1132
1324
|
const raw = this.config.baseUrl ?? "http://localhost:11434";
|
|
1133
1325
|
this.baseUrl = raw.replace(/\/v1\/?$/, "").replace(/\/+$/, "");
|
|
1134
1326
|
this.apiKey = this.config.apiKey ?? "";
|
|
1327
|
+
const cw = lookupContextWindow(this.config.model);
|
|
1328
|
+
if (cw) {
|
|
1329
|
+
this.contextWindow = cw;
|
|
1330
|
+
} else {
|
|
1331
|
+
await this.fetchModelContextWindow();
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
async fetchModelContextWindow() {
|
|
1335
|
+
try {
|
|
1336
|
+
const res = await fetch(`${this.baseUrl}/api/show`, {
|
|
1337
|
+
method: "POST",
|
|
1338
|
+
headers: this.getHeaders(),
|
|
1339
|
+
body: JSON.stringify({ name: this.config.model })
|
|
1340
|
+
});
|
|
1341
|
+
if (!res.ok)
|
|
1342
|
+
return;
|
|
1343
|
+
const data = await res.json();
|
|
1344
|
+
const info = data.model_info ?? {};
|
|
1345
|
+
const ctxKey = Object.keys(info).find((k) => k.includes("context_length") || k === "num_ctx");
|
|
1346
|
+
const ctxLen = ctxKey ? Number(info[ctxKey]) : 0;
|
|
1347
|
+
if (ctxLen > 0) {
|
|
1348
|
+
this.contextWindow = {
|
|
1349
|
+
maxInputTokens: ctxLen,
|
|
1350
|
+
maxOutputTokens: Math.min(ctxLen, 4096)
|
|
1351
|
+
};
|
|
1352
|
+
}
|
|
1353
|
+
} catch {
|
|
1354
|
+
}
|
|
1135
1355
|
}
|
|
1136
1356
|
getHeaders() {
|
|
1137
1357
|
const headers = { "Content-Type": "application/json" };
|
|
@@ -1414,6 +1634,29 @@ var init_provider_factory = __esm({
|
|
|
1414
1634
|
});
|
|
1415
1635
|
|
|
1416
1636
|
// ../llm/dist/prompt-builder.js
|
|
1637
|
+
function estimateTokens(text) {
|
|
1638
|
+
return Math.ceil(text.length / 3.5);
|
|
1639
|
+
}
|
|
1640
|
+
function estimateMessageTokens(msg) {
|
|
1641
|
+
if (typeof msg.content === "string") {
|
|
1642
|
+
return estimateTokens(msg.content) + 4;
|
|
1643
|
+
}
|
|
1644
|
+
let tokens = 4;
|
|
1645
|
+
for (const block of msg.content) {
|
|
1646
|
+
switch (block.type) {
|
|
1647
|
+
case "text":
|
|
1648
|
+
tokens += estimateTokens(block.text);
|
|
1649
|
+
break;
|
|
1650
|
+
case "tool_use":
|
|
1651
|
+
tokens += estimateTokens(block.name) + estimateTokens(JSON.stringify(block.input));
|
|
1652
|
+
break;
|
|
1653
|
+
case "tool_result":
|
|
1654
|
+
tokens += estimateTokens(block.content);
|
|
1655
|
+
break;
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
return tokens;
|
|
1659
|
+
}
|
|
1417
1660
|
var PromptBuilder;
|
|
1418
1661
|
var init_prompt_builder = __esm({
|
|
1419
1662
|
"../llm/dist/prompt-builder.js"() {
|
|
@@ -2072,31 +2315,212 @@ var init_web_search = __esm({
|
|
|
2072
2315
|
"use strict";
|
|
2073
2316
|
init_skill();
|
|
2074
2317
|
WebSearchSkill = class extends Skill {
|
|
2318
|
+
config;
|
|
2075
2319
|
metadata = {
|
|
2076
2320
|
name: "web_search",
|
|
2077
|
-
description: "Search the web
|
|
2321
|
+
description: "Search the web for current information",
|
|
2078
2322
|
riskLevel: "read",
|
|
2079
|
-
version: "
|
|
2323
|
+
version: "1.1.0",
|
|
2080
2324
|
inputSchema: {
|
|
2081
2325
|
type: "object",
|
|
2082
2326
|
properties: {
|
|
2083
2327
|
query: {
|
|
2084
2328
|
type: "string",
|
|
2085
|
-
description: "
|
|
2329
|
+
description: "The search query"
|
|
2330
|
+
},
|
|
2331
|
+
count: {
|
|
2332
|
+
type: "number",
|
|
2333
|
+
description: "Number of results to return (default: 5, max: 10)"
|
|
2086
2334
|
}
|
|
2087
2335
|
},
|
|
2088
2336
|
required: ["query"]
|
|
2089
2337
|
}
|
|
2090
2338
|
};
|
|
2339
|
+
constructor(config) {
|
|
2340
|
+
super();
|
|
2341
|
+
this.config = config;
|
|
2342
|
+
}
|
|
2091
2343
|
async execute(input2, _context) {
|
|
2092
2344
|
const query = input2.query;
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2345
|
+
const count = Math.min(Math.max(1, input2.count || 5), 10);
|
|
2346
|
+
if (!query || typeof query !== "string") {
|
|
2347
|
+
return { success: false, error: 'Invalid input: "query" must be a non-empty string' };
|
|
2348
|
+
}
|
|
2349
|
+
if (!this.config) {
|
|
2350
|
+
return {
|
|
2351
|
+
success: false,
|
|
2352
|
+
error: "Web search is not configured. Run `alfred setup` to configure a search provider."
|
|
2353
|
+
};
|
|
2354
|
+
}
|
|
2355
|
+
const needsKey = this.config.provider === "brave" || this.config.provider === "tavily";
|
|
2356
|
+
if (needsKey && !this.config.apiKey) {
|
|
2357
|
+
return {
|
|
2358
|
+
success: false,
|
|
2359
|
+
error: `Web search requires an API key for ${this.config.provider}. Run \`alfred setup\` to configure it.`
|
|
2360
|
+
};
|
|
2361
|
+
}
|
|
2362
|
+
try {
|
|
2363
|
+
let results;
|
|
2364
|
+
switch (this.config.provider) {
|
|
2365
|
+
case "brave":
|
|
2366
|
+
results = await this.searchBrave(query, count);
|
|
2367
|
+
break;
|
|
2368
|
+
case "searxng":
|
|
2369
|
+
results = await this.searchSearXNG(query, count);
|
|
2370
|
+
break;
|
|
2371
|
+
case "tavily":
|
|
2372
|
+
results = await this.searchTavily(query, count);
|
|
2373
|
+
break;
|
|
2374
|
+
case "duckduckgo":
|
|
2375
|
+
results = await this.searchDuckDuckGo(query, count);
|
|
2376
|
+
break;
|
|
2377
|
+
default:
|
|
2378
|
+
return { success: false, error: `Unknown search provider: ${this.config.provider}` };
|
|
2379
|
+
}
|
|
2380
|
+
if (results.length === 0) {
|
|
2381
|
+
return {
|
|
2382
|
+
success: true,
|
|
2383
|
+
data: { results: [] },
|
|
2384
|
+
display: `No results found for "${query}".`
|
|
2385
|
+
};
|
|
2386
|
+
}
|
|
2387
|
+
const display = results.map((r, i) => `${i + 1}. **${r.title}**
|
|
2388
|
+
${r.url}
|
|
2389
|
+
${r.snippet}`).join("\n\n");
|
|
2390
|
+
return {
|
|
2391
|
+
success: true,
|
|
2392
|
+
data: { query, results },
|
|
2393
|
+
display: `Search results for "${query}":
|
|
2394
|
+
|
|
2395
|
+
${display}`
|
|
2396
|
+
};
|
|
2397
|
+
} catch (err) {
|
|
2398
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2399
|
+
return { success: false, error: `Search failed: ${msg}` };
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
// ── Brave Search ──────────────────────────────────────────────
|
|
2403
|
+
async searchBrave(query, count) {
|
|
2404
|
+
const url = new URL("https://api.search.brave.com/res/v1/web/search");
|
|
2405
|
+
url.searchParams.set("q", query);
|
|
2406
|
+
url.searchParams.set("count", String(count));
|
|
2407
|
+
const response = await fetch(url.toString(), {
|
|
2408
|
+
headers: {
|
|
2409
|
+
"Accept": "application/json",
|
|
2410
|
+
"Accept-Encoding": "gzip",
|
|
2411
|
+
"X-Subscription-Token": this.config.apiKey
|
|
2412
|
+
}
|
|
2413
|
+
});
|
|
2414
|
+
if (!response.ok) {
|
|
2415
|
+
throw new Error(`Brave Search API returned ${response.status}: ${response.statusText}`);
|
|
2416
|
+
}
|
|
2417
|
+
const data = await response.json();
|
|
2418
|
+
return (data.web?.results ?? []).slice(0, count).map((r) => ({
|
|
2419
|
+
title: r.title,
|
|
2420
|
+
url: r.url,
|
|
2421
|
+
snippet: r.description
|
|
2422
|
+
}));
|
|
2423
|
+
}
|
|
2424
|
+
// ── SearXNG ───────────────────────────────────────────────────
|
|
2425
|
+
async searchSearXNG(query, count) {
|
|
2426
|
+
const base = (this.config.baseUrl ?? "http://localhost:8080").replace(/\/+$/, "");
|
|
2427
|
+
const url = new URL(`${base}/search`);
|
|
2428
|
+
url.searchParams.set("q", query);
|
|
2429
|
+
url.searchParams.set("format", "json");
|
|
2430
|
+
url.searchParams.set("pageno", "1");
|
|
2431
|
+
const response = await fetch(url.toString(), {
|
|
2432
|
+
headers: { "Accept": "application/json" }
|
|
2433
|
+
});
|
|
2434
|
+
if (!response.ok) {
|
|
2435
|
+
throw new Error(`SearXNG returned ${response.status}: ${response.statusText}`);
|
|
2436
|
+
}
|
|
2437
|
+
const data = await response.json();
|
|
2438
|
+
return (data.results ?? []).slice(0, count).map((r) => ({
|
|
2439
|
+
title: r.title,
|
|
2440
|
+
url: r.url,
|
|
2441
|
+
snippet: r.content
|
|
2442
|
+
}));
|
|
2443
|
+
}
|
|
2444
|
+
// ── Tavily ────────────────────────────────────────────────────
|
|
2445
|
+
async searchTavily(query, count) {
|
|
2446
|
+
const response = await fetch("https://api.tavily.com/search", {
|
|
2447
|
+
method: "POST",
|
|
2448
|
+
headers: { "Content-Type": "application/json" },
|
|
2449
|
+
body: JSON.stringify({
|
|
2450
|
+
api_key: this.config.apiKey,
|
|
2451
|
+
query,
|
|
2452
|
+
max_results: count,
|
|
2453
|
+
include_answer: false
|
|
2454
|
+
})
|
|
2455
|
+
});
|
|
2456
|
+
if (!response.ok) {
|
|
2457
|
+
throw new Error(`Tavily API returned ${response.status}: ${response.statusText}`);
|
|
2458
|
+
}
|
|
2459
|
+
const data = await response.json();
|
|
2460
|
+
return (data.results ?? []).slice(0, count).map((r) => ({
|
|
2461
|
+
title: r.title,
|
|
2462
|
+
url: r.url,
|
|
2463
|
+
snippet: r.content
|
|
2464
|
+
}));
|
|
2465
|
+
}
|
|
2466
|
+
// ── DuckDuckGo (HTML scraping, no API key) ────────────────────
|
|
2467
|
+
async searchDuckDuckGo(query, count) {
|
|
2468
|
+
const url = new URL("https://html.duckduckgo.com/html/");
|
|
2469
|
+
url.searchParams.set("q", query);
|
|
2470
|
+
const response = await fetch(url.toString(), {
|
|
2471
|
+
headers: {
|
|
2472
|
+
"User-Agent": "Mozilla/5.0 (compatible; Alfred/1.0)"
|
|
2473
|
+
}
|
|
2474
|
+
});
|
|
2475
|
+
if (!response.ok) {
|
|
2476
|
+
throw new Error(`DuckDuckGo returned ${response.status}: ${response.statusText}`);
|
|
2477
|
+
}
|
|
2478
|
+
const html = await response.text();
|
|
2479
|
+
return this.parseDuckDuckGoHtml(html, count);
|
|
2480
|
+
}
|
|
2481
|
+
parseDuckDuckGoHtml(html, count) {
|
|
2482
|
+
const results = [];
|
|
2483
|
+
const linkRegex = /<a[^>]+class="result__a"[^>]+href="([^"]*)"[^>]*>([\s\S]*?)<\/a>/g;
|
|
2484
|
+
const snippetRegex = /<a[^>]+class="result__snippet"[^>]*>([\s\S]*?)<\/a>/g;
|
|
2485
|
+
const links = [];
|
|
2486
|
+
let match;
|
|
2487
|
+
while ((match = linkRegex.exec(html)) !== null) {
|
|
2488
|
+
const rawUrl = match[1];
|
|
2489
|
+
const title = this.stripHtml(match[2]).trim();
|
|
2490
|
+
const actualUrl = this.extractDdgUrl(rawUrl);
|
|
2491
|
+
if (title && actualUrl) {
|
|
2492
|
+
links.push({ url: actualUrl, title });
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
2495
|
+
const snippets = [];
|
|
2496
|
+
while ((match = snippetRegex.exec(html)) !== null) {
|
|
2497
|
+
snippets.push(this.stripHtml(match[1]).trim());
|
|
2498
|
+
}
|
|
2499
|
+
for (let i = 0; i < Math.min(links.length, count); i++) {
|
|
2500
|
+
results.push({
|
|
2501
|
+
title: links[i].title,
|
|
2502
|
+
url: links[i].url,
|
|
2503
|
+
snippet: snippets[i] ?? ""
|
|
2504
|
+
});
|
|
2505
|
+
}
|
|
2506
|
+
return results;
|
|
2507
|
+
}
|
|
2508
|
+
extractDdgUrl(rawUrl) {
|
|
2509
|
+
try {
|
|
2510
|
+
if (rawUrl.includes("uddg=")) {
|
|
2511
|
+
const parsed = new URL(rawUrl, "https://duckduckgo.com");
|
|
2512
|
+
const uddg = parsed.searchParams.get("uddg");
|
|
2513
|
+
if (uddg)
|
|
2514
|
+
return decodeURIComponent(uddg);
|
|
2515
|
+
}
|
|
2516
|
+
} catch {
|
|
2517
|
+
}
|
|
2518
|
+
if (rawUrl.startsWith("http"))
|
|
2519
|
+
return rawUrl;
|
|
2520
|
+
return "";
|
|
2521
|
+
}
|
|
2522
|
+
stripHtml(html) {
|
|
2523
|
+
return html.replace(/<[^>]*>/g, "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/ /g, " ").replace(/\s+/g, " ");
|
|
2100
2524
|
}
|
|
2101
2525
|
};
|
|
2102
2526
|
}
|
|
@@ -3026,6 +3450,328 @@ Additional context: ${additionalContext}`;
|
|
|
3026
3450
|
}
|
|
3027
3451
|
});
|
|
3028
3452
|
|
|
3453
|
+
// ../skills/dist/built-in/email.js
|
|
3454
|
+
var EmailSkill;
|
|
3455
|
+
var init_email = __esm({
|
|
3456
|
+
"../skills/dist/built-in/email.js"() {
|
|
3457
|
+
"use strict";
|
|
3458
|
+
init_skill();
|
|
3459
|
+
EmailSkill = class extends Skill {
|
|
3460
|
+
config;
|
|
3461
|
+
metadata = {
|
|
3462
|
+
name: "email",
|
|
3463
|
+
description: "Read, search, and send emails via IMAP/SMTP",
|
|
3464
|
+
riskLevel: "write",
|
|
3465
|
+
version: "1.0.0",
|
|
3466
|
+
inputSchema: {
|
|
3467
|
+
type: "object",
|
|
3468
|
+
properties: {
|
|
3469
|
+
action: {
|
|
3470
|
+
type: "string",
|
|
3471
|
+
enum: ["inbox", "read", "search", "send"],
|
|
3472
|
+
description: "The email action to perform"
|
|
3473
|
+
},
|
|
3474
|
+
count: {
|
|
3475
|
+
type: "number",
|
|
3476
|
+
description: "Number of emails to fetch (for inbox, default: 10)"
|
|
3477
|
+
},
|
|
3478
|
+
messageId: {
|
|
3479
|
+
type: "string",
|
|
3480
|
+
description: "Message sequence number to read (for read action)"
|
|
3481
|
+
},
|
|
3482
|
+
query: {
|
|
3483
|
+
type: "string",
|
|
3484
|
+
description: "Search query (for search action)"
|
|
3485
|
+
},
|
|
3486
|
+
to: {
|
|
3487
|
+
type: "string",
|
|
3488
|
+
description: "Recipient email address (for send action)"
|
|
3489
|
+
},
|
|
3490
|
+
subject: {
|
|
3491
|
+
type: "string",
|
|
3492
|
+
description: "Email subject (for send action)"
|
|
3493
|
+
},
|
|
3494
|
+
body: {
|
|
3495
|
+
type: "string",
|
|
3496
|
+
description: "Email body text (for send action)"
|
|
3497
|
+
}
|
|
3498
|
+
},
|
|
3499
|
+
required: ["action"]
|
|
3500
|
+
}
|
|
3501
|
+
};
|
|
3502
|
+
constructor(config) {
|
|
3503
|
+
super();
|
|
3504
|
+
this.config = config;
|
|
3505
|
+
}
|
|
3506
|
+
async execute(input2, _context) {
|
|
3507
|
+
if (!this.config) {
|
|
3508
|
+
return {
|
|
3509
|
+
success: false,
|
|
3510
|
+
error: "Email is not configured. Run `alfred setup` to configure email access."
|
|
3511
|
+
};
|
|
3512
|
+
}
|
|
3513
|
+
const action = input2.action;
|
|
3514
|
+
try {
|
|
3515
|
+
switch (action) {
|
|
3516
|
+
case "inbox":
|
|
3517
|
+
return await this.fetchInbox(input2.count);
|
|
3518
|
+
case "read":
|
|
3519
|
+
return await this.readMessage(input2.messageId);
|
|
3520
|
+
case "search":
|
|
3521
|
+
return await this.searchMessages(input2.query, input2.count);
|
|
3522
|
+
case "send":
|
|
3523
|
+
return await this.sendMessage(input2.to, input2.subject, input2.body);
|
|
3524
|
+
default:
|
|
3525
|
+
return { success: false, error: `Unknown action: ${action}. Use: inbox, read, search, send` };
|
|
3526
|
+
}
|
|
3527
|
+
} catch (err) {
|
|
3528
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3529
|
+
return { success: false, error: `Email error: ${msg}` };
|
|
3530
|
+
}
|
|
3531
|
+
}
|
|
3532
|
+
// ── IMAP: Fetch inbox ──────────────────────────────────────────
|
|
3533
|
+
async fetchInbox(count) {
|
|
3534
|
+
const limit = Math.min(Math.max(1, count ?? 10), 50);
|
|
3535
|
+
const { ImapFlow } = await import("imapflow");
|
|
3536
|
+
const client = new ImapFlow({
|
|
3537
|
+
host: this.config.imap.host,
|
|
3538
|
+
port: this.config.imap.port,
|
|
3539
|
+
secure: this.config.imap.secure,
|
|
3540
|
+
auth: this.config.auth,
|
|
3541
|
+
logger: false
|
|
3542
|
+
});
|
|
3543
|
+
try {
|
|
3544
|
+
await client.connect();
|
|
3545
|
+
const lock = await client.getMailboxLock("INBOX");
|
|
3546
|
+
try {
|
|
3547
|
+
const messages = [];
|
|
3548
|
+
const mb = client.mailbox;
|
|
3549
|
+
const totalMessages = mb && typeof mb === "object" ? mb.exists ?? 0 : 0;
|
|
3550
|
+
if (totalMessages === 0) {
|
|
3551
|
+
return { success: true, data: { messages: [] }, display: "Inbox is empty." };
|
|
3552
|
+
}
|
|
3553
|
+
const startSeq = Math.max(1, totalMessages - limit + 1);
|
|
3554
|
+
const range = `${startSeq}:*`;
|
|
3555
|
+
for await (const msg of client.fetch(range, {
|
|
3556
|
+
envelope: true,
|
|
3557
|
+
flags: true
|
|
3558
|
+
})) {
|
|
3559
|
+
const from = msg.envelope?.from?.[0];
|
|
3560
|
+
const fromStr = from ? from.name ? `${from.name} <${from.address}>` : from.address ?? "unknown" : "unknown";
|
|
3561
|
+
messages.push({
|
|
3562
|
+
seq: msg.seq,
|
|
3563
|
+
from: fromStr,
|
|
3564
|
+
subject: msg.envelope?.subject ?? "(no subject)",
|
|
3565
|
+
date: msg.envelope?.date?.toISOString() ?? "",
|
|
3566
|
+
seen: msg.flags?.has("\\Seen") ?? false
|
|
3567
|
+
});
|
|
3568
|
+
}
|
|
3569
|
+
messages.reverse();
|
|
3570
|
+
const display = messages.length === 0 ? "No messages found." : messages.map((m, i) => {
|
|
3571
|
+
const unread = m.seen ? "" : " [UNREAD]";
|
|
3572
|
+
return `${i + 1}. [#${m.seq}]${unread} ${m.subject}
|
|
3573
|
+
From: ${m.from}
|
|
3574
|
+
Date: ${m.date}`;
|
|
3575
|
+
}).join("\n\n");
|
|
3576
|
+
const unreadCount = messages.filter((m) => !m.seen).length;
|
|
3577
|
+
return {
|
|
3578
|
+
success: true,
|
|
3579
|
+
data: { messages, totalMessages, unreadCount },
|
|
3580
|
+
display: `Inbox (${totalMessages} total, ${unreadCount} unread):
|
|
3581
|
+
|
|
3582
|
+
${display}`
|
|
3583
|
+
};
|
|
3584
|
+
} finally {
|
|
3585
|
+
lock.release();
|
|
3586
|
+
}
|
|
3587
|
+
} finally {
|
|
3588
|
+
await client.logout();
|
|
3589
|
+
}
|
|
3590
|
+
}
|
|
3591
|
+
// ── IMAP: Read single message ──────────────────────────────────
|
|
3592
|
+
async readMessage(messageId) {
|
|
3593
|
+
if (!messageId) {
|
|
3594
|
+
return { success: false, error: "messageId is required. Use the sequence number from inbox." };
|
|
3595
|
+
}
|
|
3596
|
+
const seq = parseInt(messageId, 10);
|
|
3597
|
+
if (isNaN(seq) || seq < 1) {
|
|
3598
|
+
return { success: false, error: "messageId must be a positive number (sequence number)." };
|
|
3599
|
+
}
|
|
3600
|
+
const { ImapFlow } = await import("imapflow");
|
|
3601
|
+
const client = new ImapFlow({
|
|
3602
|
+
host: this.config.imap.host,
|
|
3603
|
+
port: this.config.imap.port,
|
|
3604
|
+
secure: this.config.imap.secure,
|
|
3605
|
+
auth: this.config.auth,
|
|
3606
|
+
logger: false
|
|
3607
|
+
});
|
|
3608
|
+
try {
|
|
3609
|
+
await client.connect();
|
|
3610
|
+
const lock = await client.getMailboxLock("INBOX");
|
|
3611
|
+
try {
|
|
3612
|
+
const msg = await client.fetchOne(String(seq), {
|
|
3613
|
+
envelope: true,
|
|
3614
|
+
source: true
|
|
3615
|
+
});
|
|
3616
|
+
if (!msg) {
|
|
3617
|
+
return { success: false, error: `Message #${seq} not found.` };
|
|
3618
|
+
}
|
|
3619
|
+
const from = msg.envelope?.from?.[0];
|
|
3620
|
+
const fromStr = from ? from.name ? `${from.name} <${from.address}>` : from.address ?? "unknown" : "unknown";
|
|
3621
|
+
const to = msg.envelope?.to?.map((t) => t.name ? `${t.name} <${t.address}>` : t.address ?? "").join(", ") ?? "";
|
|
3622
|
+
const rawSource = msg.source?.toString() ?? "";
|
|
3623
|
+
const body = this.extractTextBody(rawSource);
|
|
3624
|
+
return {
|
|
3625
|
+
success: true,
|
|
3626
|
+
data: {
|
|
3627
|
+
seq,
|
|
3628
|
+
from: fromStr,
|
|
3629
|
+
to,
|
|
3630
|
+
subject: msg.envelope?.subject ?? "(no subject)",
|
|
3631
|
+
date: msg.envelope?.date?.toISOString() ?? "",
|
|
3632
|
+
body
|
|
3633
|
+
},
|
|
3634
|
+
display: [
|
|
3635
|
+
`From: ${fromStr}`,
|
|
3636
|
+
`To: ${to}`,
|
|
3637
|
+
`Subject: ${msg.envelope?.subject ?? "(no subject)"}`,
|
|
3638
|
+
`Date: ${msg.envelope?.date?.toISOString() ?? ""}`,
|
|
3639
|
+
"",
|
|
3640
|
+
body.slice(0, 3e3) + (body.length > 3e3 ? "\n\n... (truncated)" : "")
|
|
3641
|
+
].join("\n")
|
|
3642
|
+
};
|
|
3643
|
+
} finally {
|
|
3644
|
+
lock.release();
|
|
3645
|
+
}
|
|
3646
|
+
} finally {
|
|
3647
|
+
await client.logout();
|
|
3648
|
+
}
|
|
3649
|
+
}
|
|
3650
|
+
// ── IMAP: Search messages ──────────────────────────────────────
|
|
3651
|
+
async searchMessages(query, count) {
|
|
3652
|
+
if (!query) {
|
|
3653
|
+
return { success: false, error: "query is required for search." };
|
|
3654
|
+
}
|
|
3655
|
+
const limit = Math.min(Math.max(1, count ?? 10), 50);
|
|
3656
|
+
const { ImapFlow } = await import("imapflow");
|
|
3657
|
+
const client = new ImapFlow({
|
|
3658
|
+
host: this.config.imap.host,
|
|
3659
|
+
port: this.config.imap.port,
|
|
3660
|
+
secure: this.config.imap.secure,
|
|
3661
|
+
auth: this.config.auth,
|
|
3662
|
+
logger: false
|
|
3663
|
+
});
|
|
3664
|
+
try {
|
|
3665
|
+
await client.connect();
|
|
3666
|
+
const lock = await client.getMailboxLock("INBOX");
|
|
3667
|
+
try {
|
|
3668
|
+
const rawResult = await client.search({
|
|
3669
|
+
or: [
|
|
3670
|
+
{ subject: query },
|
|
3671
|
+
{ from: query },
|
|
3672
|
+
{ body: query }
|
|
3673
|
+
]
|
|
3674
|
+
});
|
|
3675
|
+
const searchResult = Array.isArray(rawResult) ? rawResult : [];
|
|
3676
|
+
if (searchResult.length === 0) {
|
|
3677
|
+
return { success: true, data: { results: [] }, display: `No emails found for "${query}".` };
|
|
3678
|
+
}
|
|
3679
|
+
const seqNums = searchResult.slice(-limit);
|
|
3680
|
+
const messages = [];
|
|
3681
|
+
for await (const msg of client.fetch(seqNums, { envelope: true })) {
|
|
3682
|
+
const from = msg.envelope?.from?.[0];
|
|
3683
|
+
const fromStr = from ? from.name ? `${from.name} <${from.address}>` : from.address ?? "unknown" : "unknown";
|
|
3684
|
+
messages.push({
|
|
3685
|
+
seq: msg.seq,
|
|
3686
|
+
from: fromStr,
|
|
3687
|
+
subject: msg.envelope?.subject ?? "(no subject)",
|
|
3688
|
+
date: msg.envelope?.date?.toISOString() ?? ""
|
|
3689
|
+
});
|
|
3690
|
+
}
|
|
3691
|
+
messages.reverse();
|
|
3692
|
+
const display = messages.map((m, i) => `${i + 1}. [#${m.seq}] ${m.subject}
|
|
3693
|
+
From: ${m.from}
|
|
3694
|
+
Date: ${m.date}`).join("\n\n");
|
|
3695
|
+
return {
|
|
3696
|
+
success: true,
|
|
3697
|
+
data: { query, results: messages, totalMatches: seqNums.length },
|
|
3698
|
+
display: `Search results for "${query}" (${seqNums.length} matches):
|
|
3699
|
+
|
|
3700
|
+
${display}`
|
|
3701
|
+
};
|
|
3702
|
+
} finally {
|
|
3703
|
+
lock.release();
|
|
3704
|
+
}
|
|
3705
|
+
} finally {
|
|
3706
|
+
await client.logout();
|
|
3707
|
+
}
|
|
3708
|
+
}
|
|
3709
|
+
// ── SMTP: Send message ─────────────────────────────────────────
|
|
3710
|
+
async sendMessage(to, subject, body) {
|
|
3711
|
+
if (!to)
|
|
3712
|
+
return { success: false, error: '"to" (recipient email) is required.' };
|
|
3713
|
+
if (!subject)
|
|
3714
|
+
return { success: false, error: '"subject" is required.' };
|
|
3715
|
+
if (!body)
|
|
3716
|
+
return { success: false, error: '"body" is required.' };
|
|
3717
|
+
const nodemailer = await import("nodemailer");
|
|
3718
|
+
const transport = nodemailer.createTransport({
|
|
3719
|
+
host: this.config.smtp.host,
|
|
3720
|
+
port: this.config.smtp.port,
|
|
3721
|
+
secure: this.config.smtp.secure,
|
|
3722
|
+
auth: this.config.auth
|
|
3723
|
+
});
|
|
3724
|
+
const info = await transport.sendMail({
|
|
3725
|
+
from: this.config.auth.user,
|
|
3726
|
+
to,
|
|
3727
|
+
subject,
|
|
3728
|
+
text: body
|
|
3729
|
+
});
|
|
3730
|
+
return {
|
|
3731
|
+
success: true,
|
|
3732
|
+
data: { messageId: info.messageId, to, subject },
|
|
3733
|
+
display: `Email sent to ${to}
|
|
3734
|
+
Subject: ${subject}
|
|
3735
|
+
Message ID: ${info.messageId}`
|
|
3736
|
+
};
|
|
3737
|
+
}
|
|
3738
|
+
// ── Helper: extract text body from raw email source ────────────
|
|
3739
|
+
extractTextBody(rawSource) {
|
|
3740
|
+
const parts = rawSource.split(/\r?\n\r?\n/);
|
|
3741
|
+
if (parts.length < 2)
|
|
3742
|
+
return rawSource;
|
|
3743
|
+
const headers = parts[0].toLowerCase();
|
|
3744
|
+
if (!headers.includes("multipart")) {
|
|
3745
|
+
return this.decodeBody(parts.slice(1).join("\n\n"));
|
|
3746
|
+
}
|
|
3747
|
+
const boundaryMatch = headers.match(/boundary="?([^"\s;]+)"?/i) ?? rawSource.match(/boundary="?([^"\s;]+)"?/i);
|
|
3748
|
+
if (!boundaryMatch) {
|
|
3749
|
+
return parts.slice(1).join("\n\n").slice(0, 5e3);
|
|
3750
|
+
}
|
|
3751
|
+
const boundary = boundaryMatch[1];
|
|
3752
|
+
const sections = rawSource.split(`--${boundary}`);
|
|
3753
|
+
for (const section of sections) {
|
|
3754
|
+
const sectionLower = section.toLowerCase();
|
|
3755
|
+
if (sectionLower.includes("content-type: text/plain") || sectionLower.includes("content-type:text/plain")) {
|
|
3756
|
+
const bodyStart = section.indexOf("\n\n");
|
|
3757
|
+
if (bodyStart >= 0) {
|
|
3758
|
+
return this.decodeBody(section.slice(bodyStart + 2));
|
|
3759
|
+
}
|
|
3760
|
+
const bodyStartCr = section.indexOf("\r\n\r\n");
|
|
3761
|
+
if (bodyStartCr >= 0) {
|
|
3762
|
+
return this.decodeBody(section.slice(bodyStartCr + 4));
|
|
3763
|
+
}
|
|
3764
|
+
}
|
|
3765
|
+
}
|
|
3766
|
+
return this.decodeBody(parts.slice(1).join("\n\n").slice(0, 5e3));
|
|
3767
|
+
}
|
|
3768
|
+
decodeBody(body) {
|
|
3769
|
+
return body.replace(/=\r?\n/g, "").replace(/=([0-9A-Fa-f]{2})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16))).trim();
|
|
3770
|
+
}
|
|
3771
|
+
};
|
|
3772
|
+
}
|
|
3773
|
+
});
|
|
3774
|
+
|
|
3029
3775
|
// ../skills/dist/index.js
|
|
3030
3776
|
var init_dist6 = __esm({
|
|
3031
3777
|
"../skills/dist/index.js"() {
|
|
@@ -3045,6 +3791,7 @@ var init_dist6 = __esm({
|
|
|
3045
3791
|
init_shell();
|
|
3046
3792
|
init_memory();
|
|
3047
3793
|
init_delegate();
|
|
3794
|
+
init_email();
|
|
3048
3795
|
}
|
|
3049
3796
|
});
|
|
3050
3797
|
|
|
@@ -3077,12 +3824,13 @@ var init_conversation_manager = __esm({
|
|
|
3077
3824
|
});
|
|
3078
3825
|
|
|
3079
3826
|
// ../core/dist/message-pipeline.js
|
|
3080
|
-
var MAX_TOOL_ITERATIONS, MessagePipeline;
|
|
3827
|
+
var MAX_TOOL_ITERATIONS, TOKEN_BUDGET_RATIO, MessagePipeline;
|
|
3081
3828
|
var init_message_pipeline = __esm({
|
|
3082
3829
|
"../core/dist/message-pipeline.js"() {
|
|
3083
3830
|
"use strict";
|
|
3084
3831
|
init_dist4();
|
|
3085
3832
|
MAX_TOOL_ITERATIONS = 10;
|
|
3833
|
+
TOKEN_BUDGET_RATIO = 0.85;
|
|
3086
3834
|
MessagePipeline = class {
|
|
3087
3835
|
llm;
|
|
3088
3836
|
conversationManager;
|
|
@@ -3110,7 +3858,7 @@ var init_message_pipeline = __esm({
|
|
|
3110
3858
|
try {
|
|
3111
3859
|
const user = this.users.findOrCreate(message.platform, message.userId, message.userName, message.displayName);
|
|
3112
3860
|
const conversation = this.conversationManager.getOrCreateConversation(message.platform, message.chatId, user.id);
|
|
3113
|
-
const history = this.conversationManager.getHistory(conversation.id);
|
|
3861
|
+
const history = this.conversationManager.getHistory(conversation.id, 50);
|
|
3114
3862
|
this.conversationManager.addMessage(conversation.id, "user", message.text);
|
|
3115
3863
|
let memories;
|
|
3116
3864
|
if (this.memoryRepo) {
|
|
@@ -3120,8 +3868,9 @@ var init_message_pipeline = __esm({
|
|
|
3120
3868
|
}
|
|
3121
3869
|
}
|
|
3122
3870
|
const system = this.promptBuilder.buildSystemPrompt(memories);
|
|
3123
|
-
const
|
|
3124
|
-
|
|
3871
|
+
const allMessages = this.promptBuilder.buildMessages(history);
|
|
3872
|
+
allMessages.push({ role: "user", content: message.text });
|
|
3873
|
+
const messages = this.trimToContextWindow(system, allMessages);
|
|
3125
3874
|
const tools = this.skillRegistry ? this.promptBuilder.buildTools(this.skillRegistry.getAll().map((s) => s.metadata)) : void 0;
|
|
3126
3875
|
let response;
|
|
3127
3876
|
let iteration = 0;
|
|
@@ -3219,6 +3968,43 @@ var init_message_pipeline = __esm({
|
|
|
3219
3968
|
return { content: `Skill execution failed: ${msg}`, isError: true };
|
|
3220
3969
|
}
|
|
3221
3970
|
}
|
|
3971
|
+
/**
|
|
3972
|
+
* Trim messages to fit within the LLM's context window.
|
|
3973
|
+
* Keeps the system prompt, the latest user message, and as many
|
|
3974
|
+
* recent history messages as possible. Drops oldest messages first.
|
|
3975
|
+
* Injects a summary note when messages are trimmed.
|
|
3976
|
+
*/
|
|
3977
|
+
trimToContextWindow(system, messages) {
|
|
3978
|
+
const contextWindow = this.llm.getContextWindow();
|
|
3979
|
+
const maxInputTokens = Math.floor(contextWindow.maxInputTokens * TOKEN_BUDGET_RATIO);
|
|
3980
|
+
const systemTokens = estimateTokens(system);
|
|
3981
|
+
const latestMsg = messages[messages.length - 1];
|
|
3982
|
+
const latestTokens = estimateMessageTokens(latestMsg);
|
|
3983
|
+
const reservedTokens = systemTokens + latestTokens + 200;
|
|
3984
|
+
let availableTokens = maxInputTokens - reservedTokens;
|
|
3985
|
+
if (availableTokens <= 0) {
|
|
3986
|
+
this.logger.warn({ maxInputTokens, systemTokens, latestTokens }, "Context window very tight, sending only latest message");
|
|
3987
|
+
return [latestMsg];
|
|
3988
|
+
}
|
|
3989
|
+
const keptMessages = [];
|
|
3990
|
+
for (let i = messages.length - 2; i >= 0; i--) {
|
|
3991
|
+
const msgTokens = estimateMessageTokens(messages[i]);
|
|
3992
|
+
if (msgTokens > availableTokens)
|
|
3993
|
+
break;
|
|
3994
|
+
availableTokens -= msgTokens;
|
|
3995
|
+
keptMessages.unshift(messages[i]);
|
|
3996
|
+
}
|
|
3997
|
+
const trimmedCount = messages.length - 1 - keptMessages.length;
|
|
3998
|
+
if (trimmedCount > 0) {
|
|
3999
|
+
this.logger.info({ trimmedCount, totalMessages: messages.length, maxInputTokens }, "Trimmed conversation history to fit context window");
|
|
4000
|
+
keptMessages.unshift({
|
|
4001
|
+
role: "user",
|
|
4002
|
+
content: `[System note: ${trimmedCount} older message(s) were omitted to fit the context window. The conversation continues from the most recent messages.]`
|
|
4003
|
+
});
|
|
4004
|
+
}
|
|
4005
|
+
keptMessages.push(latestMsg);
|
|
4006
|
+
return keptMessages;
|
|
4007
|
+
}
|
|
3222
4008
|
};
|
|
3223
4009
|
}
|
|
3224
4010
|
});
|
|
@@ -3768,6 +4554,9 @@ var init_dist7 = __esm({
|
|
|
3768
4554
|
});
|
|
3769
4555
|
|
|
3770
4556
|
// ../core/dist/alfred.js
|
|
4557
|
+
import fs4 from "node:fs";
|
|
4558
|
+
import path4 from "node:path";
|
|
4559
|
+
import yaml2 from "js-yaml";
|
|
3771
4560
|
var Alfred;
|
|
3772
4561
|
var init_alfred = __esm({
|
|
3773
4562
|
"../core/dist/alfred.js"() {
|
|
@@ -3802,15 +4591,21 @@ var init_alfred = __esm({
|
|
|
3802
4591
|
const reminderRepo = new ReminderRepository(db);
|
|
3803
4592
|
this.logger.info("Storage initialized");
|
|
3804
4593
|
const ruleEngine = new RuleEngine();
|
|
4594
|
+
const rules = this.loadSecurityRules();
|
|
4595
|
+
ruleEngine.loadRules(rules);
|
|
3805
4596
|
const securityManager = new SecurityManager(ruleEngine, auditRepo, this.logger.child({ component: "security" }));
|
|
3806
|
-
this.logger.info("Security engine initialized");
|
|
4597
|
+
this.logger.info({ ruleCount: rules.length }, "Security engine initialized");
|
|
3807
4598
|
const llmProvider = createLLMProvider(this.config.llm);
|
|
3808
4599
|
await llmProvider.initialize();
|
|
3809
4600
|
this.logger.info({ provider: this.config.llm.provider, model: this.config.llm.model }, "LLM provider initialized");
|
|
3810
4601
|
const skillRegistry = new SkillRegistry();
|
|
3811
4602
|
skillRegistry.register(new CalculatorSkill());
|
|
3812
4603
|
skillRegistry.register(new SystemInfoSkill());
|
|
3813
|
-
skillRegistry.register(new WebSearchSkill(
|
|
4604
|
+
skillRegistry.register(new WebSearchSkill(this.config.search ? {
|
|
4605
|
+
provider: this.config.search.provider,
|
|
4606
|
+
apiKey: this.config.search.apiKey,
|
|
4607
|
+
baseUrl: this.config.search.baseUrl
|
|
4608
|
+
} : void 0));
|
|
3814
4609
|
skillRegistry.register(new ReminderSkill(reminderRepo));
|
|
3815
4610
|
skillRegistry.register(new NoteSkill());
|
|
3816
4611
|
skillRegistry.register(new SummarizeSkill());
|
|
@@ -3819,6 +4614,11 @@ var init_alfred = __esm({
|
|
|
3819
4614
|
skillRegistry.register(new ShellSkill());
|
|
3820
4615
|
skillRegistry.register(new MemorySkill(memoryRepo));
|
|
3821
4616
|
skillRegistry.register(new DelegateSkill(llmProvider));
|
|
4617
|
+
skillRegistry.register(new EmailSkill(this.config.email ? {
|
|
4618
|
+
imap: this.config.email.imap,
|
|
4619
|
+
smtp: this.config.email.smtp,
|
|
4620
|
+
auth: this.config.email.auth
|
|
4621
|
+
} : void 0));
|
|
3822
4622
|
this.logger.info({ skills: skillRegistry.getAll().map((s) => s.metadata.name) }, "Skills registered");
|
|
3823
4623
|
const skillSandbox = new SkillSandbox(this.logger.child({ component: "sandbox" }));
|
|
3824
4624
|
const conversationManager = new ConversationManager(conversationRepo);
|
|
@@ -3913,6 +4713,34 @@ var init_alfred = __esm({
|
|
|
3913
4713
|
this.logger.warn({ platform }, "Adapter disconnected");
|
|
3914
4714
|
});
|
|
3915
4715
|
}
|
|
4716
|
+
loadSecurityRules() {
|
|
4717
|
+
const rulesPath = path4.resolve(this.config.security.rulesPath);
|
|
4718
|
+
const rules = [];
|
|
4719
|
+
if (!fs4.existsSync(rulesPath)) {
|
|
4720
|
+
this.logger.warn({ rulesPath }, "Security rules directory not found, using default deny");
|
|
4721
|
+
return rules;
|
|
4722
|
+
}
|
|
4723
|
+
const stat = fs4.statSync(rulesPath);
|
|
4724
|
+
if (!stat.isDirectory()) {
|
|
4725
|
+
this.logger.warn({ rulesPath }, "Security rules path is not a directory");
|
|
4726
|
+
return rules;
|
|
4727
|
+
}
|
|
4728
|
+
const files = fs4.readdirSync(rulesPath).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
|
|
4729
|
+
for (const file of files) {
|
|
4730
|
+
try {
|
|
4731
|
+
const filePath = path4.join(rulesPath, file);
|
|
4732
|
+
const content = fs4.readFileSync(filePath, "utf-8");
|
|
4733
|
+
const parsed = yaml2.load(content);
|
|
4734
|
+
if (parsed?.rules && Array.isArray(parsed.rules)) {
|
|
4735
|
+
rules.push(...parsed.rules);
|
|
4736
|
+
this.logger.info({ file, count: parsed.rules.length }, "Loaded security rules");
|
|
4737
|
+
}
|
|
4738
|
+
} catch (err) {
|
|
4739
|
+
this.logger.error({ err, file }, "Failed to load security rules file");
|
|
4740
|
+
}
|
|
4741
|
+
}
|
|
4742
|
+
return rules;
|
|
4743
|
+
}
|
|
3916
4744
|
};
|
|
3917
4745
|
}
|
|
3918
4746
|
});
|
|
@@ -3995,9 +4823,9 @@ __export(setup_exports, {
|
|
|
3995
4823
|
});
|
|
3996
4824
|
import { createInterface } from "node:readline/promises";
|
|
3997
4825
|
import { stdin as input, stdout as output } from "node:process";
|
|
3998
|
-
import
|
|
3999
|
-
import
|
|
4000
|
-
import
|
|
4826
|
+
import fs5 from "node:fs";
|
|
4827
|
+
import path5 from "node:path";
|
|
4828
|
+
import yaml3 from "js-yaml";
|
|
4001
4829
|
function green(s) {
|
|
4002
4830
|
return `${GREEN}${s}${RESET}`;
|
|
4003
4831
|
}
|
|
@@ -4025,20 +4853,22 @@ function loadExistingConfig(projectRoot) {
|
|
|
4025
4853
|
const config = {};
|
|
4026
4854
|
const env = {};
|
|
4027
4855
|
let shellEnabled = false;
|
|
4028
|
-
|
|
4029
|
-
|
|
4856
|
+
let writeInGroups = false;
|
|
4857
|
+
let rateLimit = 30;
|
|
4858
|
+
const configPath = path5.join(projectRoot, "config", "default.yml");
|
|
4859
|
+
if (fs5.existsSync(configPath)) {
|
|
4030
4860
|
try {
|
|
4031
|
-
const parsed =
|
|
4861
|
+
const parsed = yaml3.load(fs5.readFileSync(configPath, "utf-8"));
|
|
4032
4862
|
if (parsed && typeof parsed === "object") {
|
|
4033
4863
|
Object.assign(config, parsed);
|
|
4034
4864
|
}
|
|
4035
4865
|
} catch {
|
|
4036
4866
|
}
|
|
4037
4867
|
}
|
|
4038
|
-
const envPath =
|
|
4039
|
-
if (
|
|
4868
|
+
const envPath = path5.join(projectRoot, ".env");
|
|
4869
|
+
if (fs5.existsSync(envPath)) {
|
|
4040
4870
|
try {
|
|
4041
|
-
const lines =
|
|
4871
|
+
const lines = fs5.readFileSync(envPath, "utf-8").split("\n");
|
|
4042
4872
|
for (const line of lines) {
|
|
4043
4873
|
const trimmed = line.trim();
|
|
4044
4874
|
if (!trimmed || trimmed.startsWith("#"))
|
|
@@ -4051,17 +4881,25 @@ function loadExistingConfig(projectRoot) {
|
|
|
4051
4881
|
} catch {
|
|
4052
4882
|
}
|
|
4053
4883
|
}
|
|
4054
|
-
const rulesPath =
|
|
4055
|
-
if (
|
|
4884
|
+
const rulesPath = path5.join(projectRoot, "config", "rules", "default-rules.yml");
|
|
4885
|
+
if (fs5.existsSync(rulesPath)) {
|
|
4056
4886
|
try {
|
|
4057
|
-
const rulesContent =
|
|
4887
|
+
const rulesContent = yaml3.load(fs5.readFileSync(rulesPath, "utf-8"));
|
|
4058
4888
|
if (rulesContent?.rules) {
|
|
4059
4889
|
shellEnabled = rulesContent.rules.some((r) => r.id === "allow-owner-admin" && r.effect === "allow");
|
|
4890
|
+
const writeDmRule = rulesContent.rules.find((r) => r.id === "allow-write-for-dm" || r.id === "allow-write-all");
|
|
4891
|
+
if (writeDmRule?.id === "allow-write-all") {
|
|
4892
|
+
writeInGroups = true;
|
|
4893
|
+
}
|
|
4894
|
+
const rlRule = rulesContent.rules.find((r) => r.id === "rate-limit-write");
|
|
4895
|
+
if (rlRule?.rateLimit?.maxInvocations) {
|
|
4896
|
+
rateLimit = rlRule.rateLimit.maxInvocations;
|
|
4897
|
+
}
|
|
4060
4898
|
}
|
|
4061
4899
|
} catch {
|
|
4062
4900
|
}
|
|
4063
4901
|
}
|
|
4064
|
-
return { config, env, shellEnabled };
|
|
4902
|
+
return { config, env, shellEnabled, writeInGroups, rateLimit };
|
|
4065
4903
|
}
|
|
4066
4904
|
async function setupCommand() {
|
|
4067
4905
|
const rl = createInterface({ input, output });
|
|
@@ -4114,6 +4952,58 @@ ${bold("Which LLM provider would you like to use?")}`);
|
|
|
4114
4952
|
const existingModel = existing.config.llm?.model ?? provider.defaultModel;
|
|
4115
4953
|
console.log("");
|
|
4116
4954
|
const model = await askWithDefault(rl, "Which model?", existingModel);
|
|
4955
|
+
const searchProviders = ["brave", "tavily", "duckduckgo", "searxng"];
|
|
4956
|
+
const existingSearchProvider = existing.config.search?.provider ?? existing.env["ALFRED_SEARCH_PROVIDER"] ?? "";
|
|
4957
|
+
const existingSearchIdx = searchProviders.indexOf(existingSearchProvider);
|
|
4958
|
+
const defaultSearchChoice = existingSearchIdx >= 0 ? existingSearchIdx + 1 : 0;
|
|
4959
|
+
console.log(`
|
|
4960
|
+
${bold("Web Search provider (for searching the internet):")}`);
|
|
4961
|
+
const searchLabels = [
|
|
4962
|
+
"Brave Search \u2014 recommended, free tier (2,000/month)",
|
|
4963
|
+
"Tavily \u2014 built for AI agents, free tier (1,000/month)",
|
|
4964
|
+
"DuckDuckGo \u2014 free, no API key needed",
|
|
4965
|
+
"SearXNG \u2014 self-hosted, no API key needed"
|
|
4966
|
+
];
|
|
4967
|
+
const mark = (i) => existingSearchIdx === i ? ` ${dim("(current)")}` : "";
|
|
4968
|
+
console.log(` ${cyan("0)")} None (disable web search)${existingSearchIdx === -1 && existingSearchProvider === "" ? ` ${dim("(current)")}` : ""}`);
|
|
4969
|
+
for (let i = 0; i < searchLabels.length; i++) {
|
|
4970
|
+
console.log(` ${cyan(String(i + 1) + ")")} ${searchLabels[i]}${mark(i)}`);
|
|
4971
|
+
}
|
|
4972
|
+
const searchChoice = await askNumber(rl, "> ", 0, searchProviders.length, defaultSearchChoice);
|
|
4973
|
+
let searchProvider;
|
|
4974
|
+
let searchApiKey = "";
|
|
4975
|
+
let searchBaseUrl = "";
|
|
4976
|
+
if (searchChoice >= 1 && searchChoice <= searchProviders.length) {
|
|
4977
|
+
searchProvider = searchProviders[searchChoice - 1];
|
|
4978
|
+
}
|
|
4979
|
+
if (searchProvider === "brave") {
|
|
4980
|
+
const existingKey = existing.env["ALFRED_SEARCH_API_KEY"] ?? "";
|
|
4981
|
+
if (existingKey) {
|
|
4982
|
+
searchApiKey = await askWithDefault(rl, " Brave Search API key", existingKey);
|
|
4983
|
+
} else {
|
|
4984
|
+
console.log(` ${dim("Get your free API key at: https://brave.com/search/api/")}`);
|
|
4985
|
+
searchApiKey = await askRequired(rl, " Brave Search API key");
|
|
4986
|
+
}
|
|
4987
|
+
console.log(` ${green(">")} Brave Search: ${dim(maskKey(searchApiKey))}`);
|
|
4988
|
+
} else if (searchProvider === "tavily") {
|
|
4989
|
+
const existingKey = existing.env["ALFRED_SEARCH_API_KEY"] ?? "";
|
|
4990
|
+
if (existingKey) {
|
|
4991
|
+
searchApiKey = await askWithDefault(rl, " Tavily API key", existingKey);
|
|
4992
|
+
} else {
|
|
4993
|
+
console.log(` ${dim("Get your free API key at: https://tavily.com/")}`);
|
|
4994
|
+
searchApiKey = await askRequired(rl, " Tavily API key");
|
|
4995
|
+
}
|
|
4996
|
+
console.log(` ${green(">")} Tavily: ${dim(maskKey(searchApiKey))}`);
|
|
4997
|
+
} else if (searchProvider === "duckduckgo") {
|
|
4998
|
+
console.log(` ${green(">")} DuckDuckGo: ${dim("no API key needed")}`);
|
|
4999
|
+
} else if (searchProvider === "searxng") {
|
|
5000
|
+
const existingSearxUrl = existing.config.search?.baseUrl ?? existing.env["ALFRED_SEARCH_BASE_URL"] ?? "http://localhost:8080";
|
|
5001
|
+
searchBaseUrl = await askWithDefault(rl, " SearXNG URL", existingSearxUrl);
|
|
5002
|
+
searchBaseUrl = searchBaseUrl.replace(/\/+$/, "");
|
|
5003
|
+
console.log(` ${green(">")} SearXNG: ${dim(searchBaseUrl)}`);
|
|
5004
|
+
} else {
|
|
5005
|
+
console.log(` ${dim("Web search disabled \u2014 you can configure it later.")}`);
|
|
5006
|
+
}
|
|
4117
5007
|
const currentlyEnabled = [];
|
|
4118
5008
|
for (let i = 0; i < PLATFORMS.length; i++) {
|
|
4119
5009
|
const p = PLATFORMS[i];
|
|
@@ -4186,8 +5076,73 @@ ${bold(platform.label + " configuration:")}`);
|
|
|
4186
5076
|
}
|
|
4187
5077
|
platformCredentials[platform.configKey] = creds;
|
|
4188
5078
|
}
|
|
5079
|
+
const existingEmailUser = existing.config.email?.auth?.user ?? existing.env["ALFRED_EMAIL_USER"] ?? "";
|
|
5080
|
+
const hasEmail = !!existingEmailUser;
|
|
5081
|
+
const emailDefault = hasEmail ? "Y/n" : "y/N";
|
|
5082
|
+
console.log(`
|
|
5083
|
+
${bold("Email access (read & send emails via IMAP/SMTP)?")}`);
|
|
5084
|
+
console.log(`${dim("Works with Gmail, Outlook, or any IMAP/SMTP provider.")}`);
|
|
5085
|
+
const emailAnswer = (await rl.question(`${YELLOW}> ${RESET}${dim(`[${emailDefault}] `)}`)).trim().toLowerCase();
|
|
5086
|
+
const enableEmail = emailAnswer === "" ? hasEmail : emailAnswer === "y" || emailAnswer === "yes";
|
|
5087
|
+
let emailUser = "";
|
|
5088
|
+
let emailPass = "";
|
|
5089
|
+
let emailImapHost = "";
|
|
5090
|
+
let emailImapPort = 993;
|
|
5091
|
+
let emailSmtpHost = "";
|
|
5092
|
+
let emailSmtpPort = 587;
|
|
5093
|
+
if (enableEmail) {
|
|
5094
|
+
console.log("");
|
|
5095
|
+
emailUser = await askWithDefault(rl, " Email address", existingEmailUser || "");
|
|
5096
|
+
if (!emailUser) {
|
|
5097
|
+
emailUser = await askRequired(rl, " Email address");
|
|
5098
|
+
}
|
|
5099
|
+
const existingPass = existing.env["ALFRED_EMAIL_PASS"] ?? "";
|
|
5100
|
+
if (existingPass) {
|
|
5101
|
+
emailPass = await askWithDefault(rl, " Password / App password", existingPass);
|
|
5102
|
+
} else {
|
|
5103
|
+
console.log(` ${dim("For Gmail: use an App Password (not your regular password)")}`);
|
|
5104
|
+
console.log(` ${dim(" \u2192 Google Account \u2192 Security \u2192 2-Step \u2192 App passwords")}`);
|
|
5105
|
+
emailPass = await askRequired(rl, " Password / App password");
|
|
5106
|
+
}
|
|
5107
|
+
const domain = emailUser.split("@")[1]?.toLowerCase() ?? "";
|
|
5108
|
+
const presets = {
|
|
5109
|
+
"gmail.com": { imap: "imap.gmail.com", smtp: "smtp.gmail.com" },
|
|
5110
|
+
"googlemail.com": { imap: "imap.gmail.com", smtp: "smtp.gmail.com" },
|
|
5111
|
+
"outlook.com": { imap: "outlook.office365.com", smtp: "smtp.office365.com" },
|
|
5112
|
+
"hotmail.com": { imap: "outlook.office365.com", smtp: "smtp.office365.com" },
|
|
5113
|
+
"live.com": { imap: "outlook.office365.com", smtp: "smtp.office365.com" },
|
|
5114
|
+
"yahoo.com": { imap: "imap.mail.yahoo.com", smtp: "smtp.mail.yahoo.com" },
|
|
5115
|
+
"icloud.com": { imap: "imap.mail.me.com", smtp: "smtp.mail.me.com" },
|
|
5116
|
+
"me.com": { imap: "imap.mail.me.com", smtp: "smtp.mail.me.com" },
|
|
5117
|
+
"gmx.de": { imap: "imap.gmx.net", smtp: "mail.gmx.net" },
|
|
5118
|
+
"gmx.net": { imap: "imap.gmx.net", smtp: "mail.gmx.net" },
|
|
5119
|
+
"web.de": { imap: "imap.web.de", smtp: "smtp.web.de" },
|
|
5120
|
+
"posteo.de": { imap: "posteo.de", smtp: "posteo.de" },
|
|
5121
|
+
"mailbox.org": { imap: "imap.mailbox.org", smtp: "smtp.mailbox.org" },
|
|
5122
|
+
"protonmail.com": { imap: "127.0.0.1", smtp: "127.0.0.1" },
|
|
5123
|
+
"proton.me": { imap: "127.0.0.1", smtp: "127.0.0.1" }
|
|
5124
|
+
};
|
|
5125
|
+
const preset = presets[domain];
|
|
5126
|
+
const defaultImap = existing.config.email?.imap?.host ?? preset?.imap ?? `imap.${domain}`;
|
|
5127
|
+
const defaultSmtp = existing.config.email?.smtp?.host ?? preset?.smtp ?? `smtp.${domain}`;
|
|
5128
|
+
const defaultImapPort = existing.config.email?.imap?.port ?? 993;
|
|
5129
|
+
const defaultSmtpPort = existing.config.email?.smtp?.port ?? 587;
|
|
5130
|
+
if (preset) {
|
|
5131
|
+
console.log(` ${green(">")} Detected ${domain} \u2014 using preset server settings`);
|
|
5132
|
+
}
|
|
5133
|
+
emailImapHost = await askWithDefault(rl, " IMAP server", defaultImap);
|
|
5134
|
+
const imapPortStr = await askWithDefault(rl, " IMAP port", String(defaultImapPort));
|
|
5135
|
+
emailImapPort = parseInt(imapPortStr, 10) || 993;
|
|
5136
|
+
emailSmtpHost = await askWithDefault(rl, " SMTP server", defaultSmtp);
|
|
5137
|
+
const smtpPortStr = await askWithDefault(rl, " SMTP port", String(defaultSmtpPort));
|
|
5138
|
+
emailSmtpPort = parseInt(smtpPortStr, 10) || 587;
|
|
5139
|
+
console.log(` ${green(">")} Email: ${dim(emailUser)} via ${dim(emailImapHost)}`);
|
|
5140
|
+
} else {
|
|
5141
|
+
console.log(` ${dim("Email disabled \u2014 you can configure it later.")}`);
|
|
5142
|
+
}
|
|
5143
|
+
console.log(`
|
|
5144
|
+
${bold("Security configuration:")}`);
|
|
4189
5145
|
const existingOwnerId = existing.config.security?.ownerUserId ?? existing.env["ALFRED_OWNER_USER_ID"] ?? "";
|
|
4190
|
-
console.log("");
|
|
4191
5146
|
let ownerUserId;
|
|
4192
5147
|
if (existingOwnerId) {
|
|
4193
5148
|
ownerUserId = await askWithDefault(rl, "Owner user ID (for elevated permissions)", existingOwnerId);
|
|
@@ -4200,21 +5155,41 @@ ${bold(platform.label + " configuration:")}`);
|
|
|
4200
5155
|
if (ownerUserId) {
|
|
4201
5156
|
const shellDefault = existing.shellEnabled ? "Y/n" : "y/N";
|
|
4202
5157
|
console.log("");
|
|
4203
|
-
console.log(
|
|
4204
|
-
console.log(
|
|
4205
|
-
|
|
4206
|
-
const shellAnswer = (await rl.question(`${YELLOW}> ${RESET}${dim(`[${shellDefault}] `)}`)).trim().toLowerCase();
|
|
5158
|
+
console.log(` ${bold("Enable shell access (admin commands) for the owner?")}`);
|
|
5159
|
+
console.log(` ${dim("Allows Alfred to execute shell commands. Only for the owner.")}`);
|
|
5160
|
+
const shellAnswer = (await rl.question(` ${YELLOW}> ${RESET}${dim(`[${shellDefault}] `)}`)).trim().toLowerCase();
|
|
4207
5161
|
if (shellAnswer === "") {
|
|
4208
5162
|
enableShell = existing.shellEnabled;
|
|
4209
5163
|
} else {
|
|
4210
5164
|
enableShell = shellAnswer === "y" || shellAnswer === "yes";
|
|
4211
5165
|
}
|
|
4212
5166
|
if (enableShell) {
|
|
4213
|
-
console.log(`
|
|
5167
|
+
console.log(` ${green(">")} Shell access ${bold("enabled")} for owner ${dim(ownerUserId)}`);
|
|
4214
5168
|
} else {
|
|
4215
|
-
console.log(`
|
|
5169
|
+
console.log(` ${dim("Shell access disabled.")}`);
|
|
4216
5170
|
}
|
|
4217
5171
|
}
|
|
5172
|
+
const writeGroupsDefault = existing.writeInGroups ? "Y/n" : "y/N";
|
|
5173
|
+
console.log("");
|
|
5174
|
+
console.log(` ${bold("Allow write actions (notes, reminders, memory) in group chats?")}`);
|
|
5175
|
+
console.log(` ${dim("By default, write actions are only allowed in DMs.")}`);
|
|
5176
|
+
const writeGroupsAnswer = (await rl.question(` ${YELLOW}> ${RESET}${dim(`[${writeGroupsDefault}] `)}`)).trim().toLowerCase();
|
|
5177
|
+
let writeInGroups;
|
|
5178
|
+
if (writeGroupsAnswer === "") {
|
|
5179
|
+
writeInGroups = existing.writeInGroups;
|
|
5180
|
+
} else {
|
|
5181
|
+
writeInGroups = writeGroupsAnswer === "y" || writeGroupsAnswer === "yes";
|
|
5182
|
+
}
|
|
5183
|
+
if (writeInGroups) {
|
|
5184
|
+
console.log(` ${green(">")} Write actions ${bold("enabled")} in groups`);
|
|
5185
|
+
} else {
|
|
5186
|
+
console.log(` ${dim("Write actions only in DMs (default).")}`);
|
|
5187
|
+
}
|
|
5188
|
+
const existingRateLimit = existing.rateLimit ?? 30;
|
|
5189
|
+
console.log("");
|
|
5190
|
+
const rateLimitStr = await askWithDefault(rl, " Rate limit (max write actions per hour per user)", String(existingRateLimit));
|
|
5191
|
+
const rateLimit = Math.max(1, parseInt(rateLimitStr, 10) || 30);
|
|
5192
|
+
console.log(` ${green(">")} Rate limit: ${bold(String(rateLimit))} per hour`);
|
|
4218
5193
|
console.log(`
|
|
4219
5194
|
${bold("Writing configuration files...")}`);
|
|
4220
5195
|
const envLines = [
|
|
@@ -4239,6 +5214,27 @@ ${bold("Writing configuration files...")}`);
|
|
|
4239
5214
|
for (const [envKey, envVal] of Object.entries(envOverrides)) {
|
|
4240
5215
|
envLines.push(`${envKey}=${envVal}`);
|
|
4241
5216
|
}
|
|
5217
|
+
envLines.push("", "# === Web Search ===", "");
|
|
5218
|
+
if (searchProvider) {
|
|
5219
|
+
envLines.push(`ALFRED_SEARCH_PROVIDER=${searchProvider}`);
|
|
5220
|
+
if (searchApiKey) {
|
|
5221
|
+
envLines.push(`ALFRED_SEARCH_API_KEY=${searchApiKey}`);
|
|
5222
|
+
}
|
|
5223
|
+
if (searchBaseUrl) {
|
|
5224
|
+
envLines.push(`ALFRED_SEARCH_BASE_URL=${searchBaseUrl}`);
|
|
5225
|
+
}
|
|
5226
|
+
} else {
|
|
5227
|
+
envLines.push("# ALFRED_SEARCH_PROVIDER=brave");
|
|
5228
|
+
envLines.push("# ALFRED_SEARCH_API_KEY=");
|
|
5229
|
+
}
|
|
5230
|
+
envLines.push("", "# === Email ===", "");
|
|
5231
|
+
if (enableEmail) {
|
|
5232
|
+
envLines.push(`ALFRED_EMAIL_USER=${emailUser}`);
|
|
5233
|
+
envLines.push(`ALFRED_EMAIL_PASS=${emailPass}`);
|
|
5234
|
+
} else {
|
|
5235
|
+
envLines.push("# ALFRED_EMAIL_USER=");
|
|
5236
|
+
envLines.push("# ALFRED_EMAIL_PASS=");
|
|
5237
|
+
}
|
|
4242
5238
|
envLines.push("", "# === Security ===", "");
|
|
4243
5239
|
if (ownerUserId) {
|
|
4244
5240
|
envLines.push(`ALFRED_OWNER_USER_ID=${ownerUserId}`);
|
|
@@ -4246,12 +5242,12 @@ ${bold("Writing configuration files...")}`);
|
|
|
4246
5242
|
envLines.push("# ALFRED_OWNER_USER_ID=");
|
|
4247
5243
|
}
|
|
4248
5244
|
envLines.push("");
|
|
4249
|
-
const envPath =
|
|
4250
|
-
|
|
5245
|
+
const envPath = path5.join(projectRoot, ".env");
|
|
5246
|
+
fs5.writeFileSync(envPath, envLines.join("\n"), "utf-8");
|
|
4251
5247
|
console.log(` ${green("+")} ${dim(".env")} written`);
|
|
4252
|
-
const configDir =
|
|
4253
|
-
if (!
|
|
4254
|
-
|
|
5248
|
+
const configDir = path5.join(projectRoot, "config");
|
|
5249
|
+
if (!fs5.existsSync(configDir)) {
|
|
5250
|
+
fs5.mkdirSync(configDir, { recursive: true });
|
|
4255
5251
|
}
|
|
4256
5252
|
const config = {
|
|
4257
5253
|
name: botName,
|
|
@@ -4285,6 +5281,20 @@ ${bold("Writing configuration files...")}`);
|
|
|
4285
5281
|
temperature: 0.7,
|
|
4286
5282
|
maxTokens: 4096
|
|
4287
5283
|
},
|
|
5284
|
+
...searchProvider ? {
|
|
5285
|
+
search: {
|
|
5286
|
+
provider: searchProvider,
|
|
5287
|
+
...searchApiKey ? { apiKey: searchApiKey } : {},
|
|
5288
|
+
...searchBaseUrl ? { baseUrl: searchBaseUrl } : {}
|
|
5289
|
+
}
|
|
5290
|
+
} : {},
|
|
5291
|
+
...enableEmail ? {
|
|
5292
|
+
email: {
|
|
5293
|
+
imap: { host: emailImapHost, port: emailImapPort, secure: emailImapPort === 993 },
|
|
5294
|
+
smtp: { host: emailSmtpHost, port: emailSmtpPort, secure: emailSmtpPort === 465 },
|
|
5295
|
+
auth: { user: emailUser, pass: emailPass }
|
|
5296
|
+
}
|
|
5297
|
+
} : {},
|
|
4288
5298
|
storage: {
|
|
4289
5299
|
path: "./data/alfred.db"
|
|
4290
5300
|
},
|
|
@@ -4301,13 +5311,13 @@ ${bold("Writing configuration files...")}`);
|
|
|
4301
5311
|
if (ownerUserId) {
|
|
4302
5312
|
config.security.ownerUserId = ownerUserId;
|
|
4303
5313
|
}
|
|
4304
|
-
const yamlStr = "# Alfred \u2014 Configuration\n# Generated by `alfred setup`\n# Edit manually or re-run `alfred setup` to reconfigure.\n\n" +
|
|
4305
|
-
const configPath =
|
|
4306
|
-
|
|
5314
|
+
const yamlStr = "# Alfred \u2014 Configuration\n# Generated by `alfred setup`\n# Edit manually or re-run `alfred setup` to reconfigure.\n\n" + yaml3.dump(config, { lineWidth: 120, noRefs: true, sortKeys: false });
|
|
5315
|
+
const configPath = path5.join(configDir, "default.yml");
|
|
5316
|
+
fs5.writeFileSync(configPath, yamlStr, "utf-8");
|
|
4307
5317
|
console.log(` ${green("+")} ${dim("config/default.yml")} written`);
|
|
4308
|
-
const rulesDir =
|
|
4309
|
-
if (!
|
|
4310
|
-
|
|
5318
|
+
const rulesDir = path5.join(configDir, "rules");
|
|
5319
|
+
if (!fs5.existsSync(rulesDir)) {
|
|
5320
|
+
fs5.mkdirSync(rulesDir, { recursive: true });
|
|
4311
5321
|
}
|
|
4312
5322
|
const ownerAdminRule = enableShell && ownerUserId ? `
|
|
4313
5323
|
# Allow admin actions (shell, etc.) for the owner only
|
|
@@ -4331,6 +5341,21 @@ ${bold("Writing configuration files...")}`);
|
|
|
4331
5341
|
# conditions:
|
|
4332
5342
|
# users: ["${ownerUserId || "YOUR_USER_ID_HERE"}"]
|
|
4333
5343
|
`;
|
|
5344
|
+
const writeRule = writeInGroups ? ` # Allow write-level skills everywhere (DMs and groups)
|
|
5345
|
+
- id: allow-write-all
|
|
5346
|
+
effect: allow
|
|
5347
|
+
priority: 200
|
|
5348
|
+
scope: global
|
|
5349
|
+
actions: ["*"]
|
|
5350
|
+
riskLevels: [write]` : ` # Allow write-level skills in DMs only
|
|
5351
|
+
- id: allow-write-for-dm
|
|
5352
|
+
effect: allow
|
|
5353
|
+
priority: 200
|
|
5354
|
+
scope: global
|
|
5355
|
+
actions: ["*"]
|
|
5356
|
+
riskLevels: [write]
|
|
5357
|
+
conditions:
|
|
5358
|
+
chatType: dm`;
|
|
4334
5359
|
const rulesYaml = `# Alfred \u2014 Default Security Rules
|
|
4335
5360
|
# Rules are evaluated in priority order (lower number = higher priority).
|
|
4336
5361
|
# First matching rule wins.
|
|
@@ -4344,17 +5369,9 @@ rules:
|
|
|
4344
5369
|
actions: ["*"]
|
|
4345
5370
|
riskLevels: [read]
|
|
4346
5371
|
|
|
4347
|
-
|
|
4348
|
-
- id: allow-write-for-dm
|
|
4349
|
-
effect: allow
|
|
4350
|
-
priority: 200
|
|
4351
|
-
scope: global
|
|
4352
|
-
actions: ["*"]
|
|
4353
|
-
riskLevels: [write]
|
|
4354
|
-
conditions:
|
|
4355
|
-
chatType: dm
|
|
5372
|
+
${writeRule}
|
|
4356
5373
|
|
|
4357
|
-
# Rate-limit write actions: max
|
|
5374
|
+
# Rate-limit write actions: max ${rateLimit} per hour per user
|
|
4358
5375
|
- id: rate-limit-write
|
|
4359
5376
|
effect: allow
|
|
4360
5377
|
priority: 250
|
|
@@ -4362,7 +5379,7 @@ rules:
|
|
|
4362
5379
|
actions: ["*"]
|
|
4363
5380
|
riskLevels: [write]
|
|
4364
5381
|
rateLimit:
|
|
4365
|
-
maxInvocations:
|
|
5382
|
+
maxInvocations: ${rateLimit}
|
|
4366
5383
|
windowSeconds: 3600
|
|
4367
5384
|
${ownerAdminRule}
|
|
4368
5385
|
# Deny destructive and admin actions by default
|
|
@@ -4381,12 +5398,12 @@ ${ownerAdminRule}
|
|
|
4381
5398
|
actions: ["*"]
|
|
4382
5399
|
riskLevels: [read, write, destructive, admin]
|
|
4383
5400
|
`;
|
|
4384
|
-
const rulesPath =
|
|
4385
|
-
|
|
5401
|
+
const rulesPath = path5.join(rulesDir, "default-rules.yml");
|
|
5402
|
+
fs5.writeFileSync(rulesPath, rulesYaml, "utf-8");
|
|
4386
5403
|
console.log(` ${green("+")} ${dim("config/rules/default-rules.yml")} written`);
|
|
4387
|
-
const dataDir =
|
|
4388
|
-
if (!
|
|
4389
|
-
|
|
5404
|
+
const dataDir = path5.join(projectRoot, "data");
|
|
5405
|
+
if (!fs5.existsSync(dataDir)) {
|
|
5406
|
+
fs5.mkdirSync(dataDir, { recursive: true });
|
|
4390
5407
|
console.log(` ${green("+")} ${dim("data/")} directory created`);
|
|
4391
5408
|
}
|
|
4392
5409
|
console.log("");
|
|
@@ -4404,10 +5421,28 @@ ${ownerAdminRule}
|
|
|
4404
5421
|
} else {
|
|
4405
5422
|
console.log(` ${bold("Platforms:")} none (configure later)`);
|
|
4406
5423
|
}
|
|
5424
|
+
if (searchProvider) {
|
|
5425
|
+
const searchLabelMap = {
|
|
5426
|
+
brave: "Brave Search",
|
|
5427
|
+
tavily: "Tavily",
|
|
5428
|
+
duckduckgo: "DuckDuckGo",
|
|
5429
|
+
searxng: `SearXNG (${searchBaseUrl})`
|
|
5430
|
+
};
|
|
5431
|
+
console.log(` ${bold("Web search:")} ${searchLabelMap[searchProvider]}`);
|
|
5432
|
+
} else {
|
|
5433
|
+
console.log(` ${bold("Web search:")} ${dim("disabled")}`);
|
|
5434
|
+
}
|
|
5435
|
+
if (enableEmail) {
|
|
5436
|
+
console.log(` ${bold("Email:")} ${emailUser} (${emailImapHost})`);
|
|
5437
|
+
} else {
|
|
5438
|
+
console.log(` ${bold("Email:")} ${dim("disabled")}`);
|
|
5439
|
+
}
|
|
4407
5440
|
if (ownerUserId) {
|
|
4408
5441
|
console.log(` ${bold("Owner ID:")} ${ownerUserId}`);
|
|
4409
5442
|
console.log(` ${bold("Shell access:")} ${enableShell ? green("enabled") : dim("disabled")}`);
|
|
4410
5443
|
}
|
|
5444
|
+
console.log(` ${bold("Write scope:")} ${writeInGroups ? "DMs + Groups" : "DMs only"}`);
|
|
5445
|
+
console.log(` ${bold("Rate limit:")} ${rateLimit}/hour per user`);
|
|
4411
5446
|
console.log("");
|
|
4412
5447
|
console.log(`${CYAN}Next steps:${RESET}`);
|
|
4413
5448
|
console.log(` ${bold("alfred start")} Start Alfred`);
|
|
@@ -4641,9 +5676,9 @@ var rules_exports = {};
|
|
|
4641
5676
|
__export(rules_exports, {
|
|
4642
5677
|
rulesCommand: () => rulesCommand
|
|
4643
5678
|
});
|
|
4644
|
-
import
|
|
4645
|
-
import
|
|
4646
|
-
import
|
|
5679
|
+
import fs6 from "node:fs";
|
|
5680
|
+
import path6 from "node:path";
|
|
5681
|
+
import yaml4 from "js-yaml";
|
|
4647
5682
|
async function rulesCommand() {
|
|
4648
5683
|
const configLoader = new ConfigLoader();
|
|
4649
5684
|
let config;
|
|
@@ -4653,18 +5688,18 @@ async function rulesCommand() {
|
|
|
4653
5688
|
console.error("Failed to load configuration:", error.message);
|
|
4654
5689
|
process.exit(1);
|
|
4655
5690
|
}
|
|
4656
|
-
const rulesPath =
|
|
4657
|
-
if (!
|
|
5691
|
+
const rulesPath = path6.resolve(config.security.rulesPath);
|
|
5692
|
+
if (!fs6.existsSync(rulesPath)) {
|
|
4658
5693
|
console.log(`Rules directory not found: ${rulesPath}`);
|
|
4659
5694
|
console.log("No security rules loaded.");
|
|
4660
5695
|
return;
|
|
4661
5696
|
}
|
|
4662
|
-
const stat =
|
|
5697
|
+
const stat = fs6.statSync(rulesPath);
|
|
4663
5698
|
if (!stat.isDirectory()) {
|
|
4664
5699
|
console.error(`Rules path is not a directory: ${rulesPath}`);
|
|
4665
5700
|
process.exit(1);
|
|
4666
5701
|
}
|
|
4667
|
-
const files =
|
|
5702
|
+
const files = fs6.readdirSync(rulesPath).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
|
|
4668
5703
|
if (files.length === 0) {
|
|
4669
5704
|
console.log(`No YAML rule files found in: ${rulesPath}`);
|
|
4670
5705
|
return;
|
|
@@ -4673,10 +5708,10 @@ async function rulesCommand() {
|
|
|
4673
5708
|
const allRules = [];
|
|
4674
5709
|
const errors = [];
|
|
4675
5710
|
for (const file of files) {
|
|
4676
|
-
const filePath =
|
|
5711
|
+
const filePath = path6.join(rulesPath, file);
|
|
4677
5712
|
try {
|
|
4678
|
-
const raw =
|
|
4679
|
-
const parsed =
|
|
5713
|
+
const raw = fs6.readFileSync(filePath, "utf-8");
|
|
5714
|
+
const parsed = yaml4.load(raw);
|
|
4680
5715
|
const rules = ruleLoader.loadFromObject(parsed);
|
|
4681
5716
|
allRules.push(...rules);
|
|
4682
5717
|
} catch (error) {
|
|
@@ -4727,9 +5762,9 @@ var status_exports = {};
|
|
|
4727
5762
|
__export(status_exports, {
|
|
4728
5763
|
statusCommand: () => statusCommand
|
|
4729
5764
|
});
|
|
4730
|
-
import
|
|
4731
|
-
import
|
|
4732
|
-
import
|
|
5765
|
+
import fs7 from "node:fs";
|
|
5766
|
+
import path7 from "node:path";
|
|
5767
|
+
import yaml5 from "js-yaml";
|
|
4733
5768
|
async function statusCommand() {
|
|
4734
5769
|
const configLoader = new ConfigLoader();
|
|
4735
5770
|
let config;
|
|
@@ -4785,23 +5820,23 @@ async function statusCommand() {
|
|
|
4785
5820
|
}
|
|
4786
5821
|
console.log("");
|
|
4787
5822
|
console.log("Storage:");
|
|
4788
|
-
const dbPath =
|
|
4789
|
-
const dbExists =
|
|
5823
|
+
const dbPath = path7.resolve(config.storage.path);
|
|
5824
|
+
const dbExists = fs7.existsSync(dbPath);
|
|
4790
5825
|
console.log(` Database: ${dbPath}`);
|
|
4791
5826
|
console.log(` Status: ${dbExists ? "exists" : "not yet created"}`);
|
|
4792
5827
|
console.log("");
|
|
4793
|
-
const rulesPath =
|
|
5828
|
+
const rulesPath = path7.resolve(config.security.rulesPath);
|
|
4794
5829
|
let ruleCount = 0;
|
|
4795
5830
|
let ruleFileCount = 0;
|
|
4796
|
-
if (
|
|
4797
|
-
const files =
|
|
5831
|
+
if (fs7.existsSync(rulesPath) && fs7.statSync(rulesPath).isDirectory()) {
|
|
5832
|
+
const files = fs7.readdirSync(rulesPath).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
|
|
4798
5833
|
ruleFileCount = files.length;
|
|
4799
5834
|
const ruleLoader = new RuleLoader();
|
|
4800
5835
|
for (const file of files) {
|
|
4801
|
-
const filePath =
|
|
5836
|
+
const filePath = path7.join(rulesPath, file);
|
|
4802
5837
|
try {
|
|
4803
|
-
const raw =
|
|
4804
|
-
const parsed =
|
|
5838
|
+
const raw = fs7.readFileSync(filePath, "utf-8");
|
|
5839
|
+
const parsed = yaml5.load(raw);
|
|
4805
5840
|
const rules = ruleLoader.loadFromObject(parsed);
|
|
4806
5841
|
ruleCount += rules.length;
|
|
4807
5842
|
} catch {
|
|
@@ -4834,8 +5869,8 @@ var logs_exports = {};
|
|
|
4834
5869
|
__export(logs_exports, {
|
|
4835
5870
|
logsCommand: () => logsCommand
|
|
4836
5871
|
});
|
|
4837
|
-
import
|
|
4838
|
-
import
|
|
5872
|
+
import fs8 from "node:fs";
|
|
5873
|
+
import path8 from "node:path";
|
|
4839
5874
|
async function logsCommand(tail) {
|
|
4840
5875
|
const configLoader = new ConfigLoader();
|
|
4841
5876
|
let config;
|
|
@@ -4845,8 +5880,8 @@ async function logsCommand(tail) {
|
|
|
4845
5880
|
console.error("Failed to load configuration:", error.message);
|
|
4846
5881
|
process.exit(1);
|
|
4847
5882
|
}
|
|
4848
|
-
const dbPath =
|
|
4849
|
-
if (!
|
|
5883
|
+
const dbPath = path8.resolve(config.storage.path);
|
|
5884
|
+
if (!fs8.existsSync(dbPath)) {
|
|
4850
5885
|
console.log(`Database not found at: ${dbPath}`);
|
|
4851
5886
|
console.log("No audit log entries. Alfred has not been run yet, or the database path is incorrect.");
|
|
4852
5887
|
return;
|