zilmate 1.3.5 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/dist/agents/automation-planner.agent.d.ts +108 -0
  2. package/dist/agents/automation-planner.agent.d.ts.map +1 -1
  3. package/dist/agents/automation-planner.agent.js +13 -4
  4. package/dist/agents/automation-planner.agent.js.map +1 -1
  5. package/dist/agents/manager.d.ts +117 -0
  6. package/dist/agents/manager.d.ts.map +1 -1
  7. package/dist/agents/manager.js +25 -1
  8. package/dist/agents/manager.js.map +1 -1
  9. package/dist/agents/security.agent.d.ts +340 -0
  10. package/dist/agents/security.agent.d.ts.map +1 -0
  11. package/dist/agents/security.agent.js +76 -0
  12. package/dist/agents/security.agent.js.map +1 -0
  13. package/dist/cli/confirm.d.ts.map +1 -1
  14. package/dist/cli/confirm.js +8 -14
  15. package/dist/cli/confirm.js.map +1 -1
  16. package/dist/cli/format.js +1 -1
  17. package/dist/cli/interactive.d.ts.map +1 -1
  18. package/dist/cli/interactive.js +2 -0
  19. package/dist/cli/interactive.js.map +1 -1
  20. package/dist/cli/triggers.d.ts.map +1 -1
  21. package/dist/cli/triggers.js +12 -4
  22. package/dist/cli/triggers.js.map +1 -1
  23. package/dist/cli/voice.d.ts.map +1 -1
  24. package/dist/cli/voice.js +2 -0
  25. package/dist/cli/voice.js.map +1 -1
  26. package/dist/config/env.d.ts +4 -0
  27. package/dist/config/env.d.ts.map +1 -1
  28. package/dist/config/env.js +4 -0
  29. package/dist/config/env.js.map +1 -1
  30. package/dist/index.js +1 -1
  31. package/dist/jobs/trigger-orchestrator.d.ts +26 -0
  32. package/dist/jobs/trigger-orchestrator.d.ts.map +1 -0
  33. package/dist/jobs/trigger-orchestrator.js +252 -0
  34. package/dist/jobs/trigger-orchestrator.js.map +1 -0
  35. package/dist/jobs/trigger-policies.d.ts +36 -0
  36. package/dist/jobs/trigger-policies.d.ts.map +1 -0
  37. package/dist/jobs/trigger-policies.js +68 -0
  38. package/dist/jobs/trigger-policies.js.map +1 -0
  39. package/dist/jobs/trigger-router.d.ts +14 -0
  40. package/dist/jobs/trigger-router.d.ts.map +1 -0
  41. package/dist/jobs/trigger-router.js +114 -0
  42. package/dist/jobs/trigger-router.js.map +1 -0
  43. package/dist/jobs/workflows.d.ts +4 -1
  44. package/dist/jobs/workflows.d.ts.map +1 -1
  45. package/dist/jobs/workflows.js +18 -42
  46. package/dist/jobs/workflows.js.map +1 -1
  47. package/dist/memory/personal-context.d.ts +45 -0
  48. package/dist/memory/personal-context.d.ts.map +1 -0
  49. package/dist/memory/personal-context.js +103 -0
  50. package/dist/memory/personal-context.js.map +1 -0
  51. package/dist/runtime/confirm.d.ts +3 -0
  52. package/dist/runtime/confirm.d.ts.map +1 -1
  53. package/dist/runtime/confirm.js +38 -12
  54. package/dist/runtime/confirm.js.map +1 -1
  55. package/dist/runtime/progress.d.ts.map +1 -1
  56. package/dist/runtime/progress.js +3 -0
  57. package/dist/runtime/progress.js.map +1 -1
  58. package/dist/server.d.ts +2 -0
  59. package/dist/server.d.ts.map +1 -1
  60. package/dist/server.js +2 -0
  61. package/dist/server.js.map +1 -1
  62. package/dist/skills/loader.d.ts +14 -0
  63. package/dist/skills/loader.d.ts.map +1 -0
  64. package/dist/skills/loader.js +105 -0
  65. package/dist/skills/loader.js.map +1 -0
  66. package/dist/tools/cli-runner.d.ts +13 -0
  67. package/dist/tools/cli-runner.d.ts.map +1 -0
  68. package/dist/tools/cli-runner.js +75 -0
  69. package/dist/tools/cli-runner.js.map +1 -0
  70. package/dist/tools/computer-use.tool.d.ts +159 -0
  71. package/dist/tools/computer-use.tool.d.ts.map +1 -0
  72. package/dist/tools/computer-use.tool.js +356 -0
  73. package/dist/tools/computer-use.tool.js.map +1 -0
  74. package/dist/tools/orchestration.tool.d.ts +83 -0
  75. package/dist/tools/orchestration.tool.d.ts.map +1 -0
  76. package/dist/tools/orchestration.tool.js +154 -0
  77. package/dist/tools/orchestration.tool.js.map +1 -0
  78. package/dist/tools/osint-install.tool.d.ts +35 -0
  79. package/dist/tools/osint-install.tool.d.ts.map +1 -0
  80. package/dist/tools/osint-install.tool.js +334 -0
  81. package/dist/tools/osint-install.tool.js.map +1 -0
  82. package/dist/tools/osint.tool.d.ts +445 -0
  83. package/dist/tools/osint.tool.d.ts.map +1 -0
  84. package/dist/tools/osint.tool.js +630 -0
  85. package/dist/tools/osint.tool.js.map +1 -0
  86. package/dist/tools/pentest-install.tool.d.ts +32 -0
  87. package/dist/tools/pentest-install.tool.d.ts.map +1 -0
  88. package/dist/tools/pentest-install.tool.js +201 -0
  89. package/dist/tools/pentest-install.tool.js.map +1 -0
  90. package/dist/tools/pentest.tool.d.ts +595 -0
  91. package/dist/tools/pentest.tool.d.ts.map +1 -0
  92. package/dist/tools/pentest.tool.js +841 -0
  93. package/dist/tools/pentest.tool.js.map +1 -0
  94. package/dist/tools/personal-context.tool.d.ts +32 -0
  95. package/dist/tools/personal-context.tool.d.ts.map +1 -0
  96. package/dist/tools/personal-context.tool.js +76 -0
  97. package/dist/tools/personal-context.tool.js.map +1 -0
  98. package/dist/tools/skills.tool.d.ts +17 -0
  99. package/dist/tools/skills.tool.d.ts.map +1 -0
  100. package/dist/tools/skills.tool.js +44 -0
  101. package/dist/tools/skills.tool.js.map +1 -0
  102. package/dist/voice/cascade.d.ts.map +1 -1
  103. package/dist/voice/cascade.js +71 -24
  104. package/dist/voice/cascade.js.map +1 -1
  105. package/dist/voice/deepgram.d.ts.map +1 -1
  106. package/dist/voice/deepgram.js +8 -1
  107. package/dist/voice/deepgram.js.map +1 -1
  108. package/dist/voice/types.d.ts +4 -0
  109. package/dist/voice/types.d.ts.map +1 -1
  110. package/package.json +1 -1
@@ -0,0 +1,630 @@
1
+ import { mkdir, writeFile } from 'node:fs/promises';
2
+ import { existsSync } from 'node:fs';
3
+ import path from 'node:path';
4
+ import { tool } from 'ai';
5
+ import { z } from 'zod';
6
+ import { requestConfirmation } from '../runtime/confirm.js';
7
+ import { emitProgress } from '../runtime/progress.js';
8
+ import { runCliTool } from './cli-runner.js';
9
+ const osintOutputDir = path.resolve('outputs', 'osint');
10
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
11
+ async function confirmOsintAction(action, details) {
12
+ return requestConfirmation({
13
+ toolkitSlug: 'ZILMATE',
14
+ toolSlug: 'OSINT',
15
+ action,
16
+ access: 'Read-only',
17
+ targetTools: ['ZILMATE_OSINT'],
18
+ details,
19
+ summary: details.join('; '),
20
+ });
21
+ }
22
+ async function ensureOutputDir(subdir) {
23
+ const dir = subdir ? path.join(osintOutputDir, subdir) : osintOutputDir;
24
+ await mkdir(dir, { recursive: true });
25
+ return dir;
26
+ }
27
+ function ts() {
28
+ return new Date().toISOString().replace(/[:.]/g, '-');
29
+ }
30
+ function sanitize(s) {
31
+ return s.replace(/[^a-z0-9_.-]/gi, '_');
32
+ }
33
+ /**
34
+ * Run a CLI tool via the shared cross-platform runner.
35
+ */
36
+ function runTool(command, args, timeoutMs = 120_000) {
37
+ return runCliTool(command, args, { timeoutMs });
38
+ }
39
+ async function saveOutput(filename, content) {
40
+ await ensureOutputDir();
41
+ const p = path.join(osintOutputDir, filename);
42
+ await writeFile(p, content, 'utf8');
43
+ return p;
44
+ }
45
+ // ─── Username Scanners ────────────────────────────────────────────────────────
46
+ export const usernameTools = {
47
+ /**
48
+ * Sherlock: sherlock [--timeout N] [--print-found] [--csv] [--xlsx]
49
+ * --folderoutput <dir> for multiple usernames
50
+ * --output <file> for a single username
51
+ * Docs: https://sherlockproject.xyz/usage
52
+ */
53
+ runSherlock: tool({
54
+ description: 'Search for a username across 300+ social networks using Sherlock. Returns found profile URLs. Fast broad sweep — best starting point for any username.',
55
+ inputSchema: z.object({
56
+ username: z.string().min(1).describe('The handle to search for.'),
57
+ timeout: z.number().int().min(5).max(120).optional().default(30).describe('Per-site request timeout in seconds (default 30).'),
58
+ csv: z.boolean().optional().default(false).describe('Also export results as CSV.'),
59
+ xlsx: z.boolean().optional().default(false).describe('Also export results as XLSX.'),
60
+ printFoundOnly: z.boolean().optional().default(true).describe('Only output sites where account was found (cleaner output).'),
61
+ }),
62
+ execute: async ({ username, timeout, csv, xlsx, printFoundOnly }) => {
63
+ const approved = await confirmOsintAction('Run Sherlock username scan', [
64
+ `Target username: ${username}`,
65
+ 'Queries 300+ social networks for matching profiles',
66
+ 'Network requests from this machine only',
67
+ ]);
68
+ if (!approved)
69
+ throw new Error('Blocked Sherlock scan. Ask user to approve.');
70
+ emitProgress({ type: 'tool:start', label: 'Sherlock scanning', detail: username });
71
+ const dir = await ensureOutputDir('sherlock');
72
+ const outFile = path.join(dir, `${sanitize(username)}-${ts()}.txt`);
73
+ const args = ['--timeout', String(timeout), '--output', outFile, '--no-color'];
74
+ if (printFoundOnly)
75
+ args.push('--print-found');
76
+ if (csv)
77
+ args.push('--csv');
78
+ if (xlsx)
79
+ args.push('--xlsx');
80
+ args.push(username);
81
+ const raw = await runTool('sherlock', args, (timeout + 10) * 10_000);
82
+ // Parse found URLs — lines starting with [+]
83
+ const found = [...raw.matchAll(/\[\+\]\s+\S+:\s+(https?:\/\/\S+)/g)].map((m) => m[1]);
84
+ emitProgress({ type: 'tool:end', label: 'Sherlock complete', detail: `${found.length} profiles found` });
85
+ return { username, found, foundCount: found.length, outputFile: outFile, raw: raw.slice(0, 4000) };
86
+ },
87
+ }),
88
+ /**
89
+ * Maigret: maigret <username> [--top-sites N] [-a] [--pdf] [--html] [--csv] [--json]
90
+ * --no-extracting skip metadata extraction
91
+ * --permute generate username variants
92
+ * Docs: https://github.com/soxoj/maigret
93
+ */
94
+ runMaigret: tool({
95
+ description: 'Deep username dossier across 3,000+ sites using Maigret. Extracts metadata, linked accounts, and builds reports. More thorough than Sherlock — use when you want full coverage.',
96
+ inputSchema: z.object({
97
+ username: z.string().min(1),
98
+ allSites: z.boolean().optional().default(false).describe('Scan all 3,000+ sites (slow). Default: top 500 by traffic.'),
99
+ permute: z.boolean().optional().default(false).describe('Also search common username variants (john_doe, j.doe, etc.).'),
100
+ format: z.enum(['json', 'pdf', 'html', 'csv', 'txt']).optional().default('json').describe('Output report format.'),
101
+ tags: z.string().optional().describe('Filter to sites with this tag (e.g. "photo", "dating", "us"). Comma-separated.'),
102
+ }),
103
+ execute: async ({ username, allSites, permute, format, tags }) => {
104
+ const approved = await confirmOsintAction('Run Maigret deep username scan', [
105
+ `Target: ${username}`,
106
+ allSites ? 'Scanning all 3,000+ sites' : 'Scanning top 500 sites by traffic',
107
+ permute ? 'Generating username permutations' : '',
108
+ `Output format: ${format}`,
109
+ ].filter(Boolean));
110
+ if (!approved)
111
+ throw new Error('Blocked Maigret scan. Ask user to approve.');
112
+ emitProgress({ type: 'tool:start', label: 'Maigret scanning', detail: username });
113
+ const dir = await ensureOutputDir('maigret');
114
+ const reportBase = path.join(dir, `${sanitize(username)}-${ts()}`);
115
+ // Maigret places reports in the current dir under reports/ — use --folderoutput
116
+ const args = ['--no-color', '--folderoutput', dir];
117
+ if (allSites)
118
+ args.push('-a');
119
+ if (permute)
120
+ args.push('--permute');
121
+ if (tags)
122
+ args.push('--tags', tags);
123
+ // Output format flags: --pdf, --html, --csv, --json, --txt
124
+ args.push(`--${format}`);
125
+ args.push(username);
126
+ const raw = await runTool('maigret', args, allSites ? 900_000 : 300_000);
127
+ emitProgress({ type: 'tool:end', label: 'Maigret scan complete', detail: username });
128
+ return { username, outputDir: dir, format, raw: raw.slice(0, 4000) };
129
+ },
130
+ }),
131
+ /**
132
+ * Blackbird (p1ngul1n0): python3 blackbird.py -u <username> -e <email> [--pdf] [--csv]
133
+ * Docs: https://github.com/p1ngul1n0/blackbird
134
+ * Note: installed as `blackbird` CLI via pip install blackbird-osint
135
+ */
136
+ runBlackbird: tool({
137
+ description: 'Search username and/or email across 600+ platforms using Blackbird (WhatsMyName dataset). Generates PDF/CSV profile reports. Best combined username+email sweep.',
138
+ inputSchema: z.object({
139
+ username: z.string().optional().describe('Username/handle to search.'),
140
+ email: z.string().email().optional().describe('Email address to search alongside.'),
141
+ pdf: z.boolean().optional().default(true).describe('Export results as PDF report.'),
142
+ csv: z.boolean().optional().default(false).describe('Export results as CSV.'),
143
+ }),
144
+ execute: async ({ username, email, pdf, csv }) => {
145
+ if (!username && !email)
146
+ throw new Error('Provide at least one of username or email.');
147
+ const approved = await confirmOsintAction('Run Blackbird scan', [
148
+ username ? `Username: ${username}` : '',
149
+ email ? `Email: ${email}` : '',
150
+ 'Searches 600+ platforms via WhatsMyName dataset',
151
+ pdf ? 'Generating PDF report' : '',
152
+ ].filter(Boolean));
153
+ if (!approved)
154
+ throw new Error('Blocked Blackbird scan. Ask user to approve.');
155
+ emitProgress({ type: 'tool:start', label: 'Blackbird scanning' });
156
+ const args = ['--no-nsfw'];
157
+ if (username)
158
+ args.push('-u', username);
159
+ if (email)
160
+ args.push('-e', email);
161
+ if (pdf)
162
+ args.push('--pdf');
163
+ if (csv)
164
+ args.push('--csv');
165
+ const raw = await runTool('blackbird', args, 240_000);
166
+ const dir = await ensureOutputDir('blackbird');
167
+ const outFile = await saveOutput(`blackbird/blackbird-${sanitize(username ?? email ?? 'scan')}-${ts()}.txt`, raw);
168
+ emitProgress({ type: 'tool:end', label: 'Blackbird complete' });
169
+ return { username, email, outputFile: outFile, raw: raw.slice(0, 4000) };
170
+ },
171
+ }),
172
+ /**
173
+ * Naminter: naminter <username>
174
+ * Uses TLS browser impersonation — bypasses Cloudflare bot detection
175
+ * Docs: https://github.com/soxoj/naminter
176
+ */
177
+ runNaminter: tool({
178
+ description: 'Username scan using TLS browser impersonation (bypasses Cloudflare/bot-detection). Use when Sherlock/Maigret get blocked on target sites.',
179
+ inputSchema: z.object({
180
+ username: z.string().min(1),
181
+ }),
182
+ execute: async ({ username }) => {
183
+ const approved = await confirmOsintAction('Run Naminter username scan', [
184
+ `Target: ${username}`,
185
+ 'Uses TLS impersonation — harder for sites to detect and block',
186
+ ]);
187
+ if (!approved)
188
+ throw new Error('Blocked Naminter scan. Ask user to approve.');
189
+ emitProgress({ type: 'tool:start', label: 'Naminter scanning', detail: username });
190
+ const raw = await runTool('naminter', [username], 240_000);
191
+ const outFile = await saveOutput(`naminter/naminter-${sanitize(username)}-${ts()}.txt`, raw);
192
+ emitProgress({ type: 'tool:end', label: 'Naminter complete' });
193
+ return { username, outputFile: outFile, raw: raw.slice(0, 4000) };
194
+ },
195
+ }),
196
+ /**
197
+ * Linkook: linkook --url <profile_url> [--depth N]
198
+ * Recursively scrapes linked profiles to find alternate usernames
199
+ * Docs: https://github.com/soxoj/linkook
200
+ */
201
+ runLinkook: tool({
202
+ description: 'Recursively scrape a profile page and hunt for alternate usernames linked from it. Best when you already have one profile URL and want to find all connected accounts.',
203
+ inputSchema: z.object({
204
+ profileUrl: z.string().url().describe('Starting profile URL, e.g. https://twitter.com/target.'),
205
+ depth: z.number().int().min(1).max(3).optional().default(1).describe('How many hops deep to follow links (1–3).'),
206
+ }),
207
+ execute: async ({ profileUrl, depth }) => {
208
+ const approved = await confirmOsintAction('Run Linkook recursive profile scan', [
209
+ `Starting URL: ${profileUrl}`,
210
+ `Recursion depth: ${depth}`,
211
+ 'Scrapes linked profiles to map connected accounts',
212
+ ]);
213
+ if (!approved)
214
+ throw new Error('Blocked Linkook scan. Ask user to approve.');
215
+ emitProgress({ type: 'tool:start', label: 'Linkook scanning', detail: profileUrl });
216
+ const args = ['--url', profileUrl, '--depth', String(depth)];
217
+ const raw = await runTool('linkook', args, 300_000);
218
+ const outFile = await saveOutput(`linkook/linkook-${ts()}.txt`, raw);
219
+ emitProgress({ type: 'tool:end', label: 'Linkook complete' });
220
+ return { profileUrl, depth, outputFile: outFile, raw: raw.slice(0, 4000) };
221
+ },
222
+ }),
223
+ };
224
+ // ─── Email & Identity Tools ───────────────────────────────────────────────────
225
+ export const emailTools = {
226
+ /**
227
+ * Holehe: holehe [--only-used] [--no-color] <email>
228
+ * Output markers: [+] = found, [-] = not found, [x] = rate limit, [!] = error
229
+ * Docs: https://github.com/megadose/holehe
230
+ */
231
+ runHolehe: tool({
232
+ description: 'Silently check if an email is registered on 120+ platforms using Holehe. Uses the forgotten-password flow — never alerts the target. Best first step when you have an email.',
233
+ inputSchema: z.object({
234
+ email: z.string().email(),
235
+ }),
236
+ execute: async ({ email }) => {
237
+ const approved = await confirmOsintAction('Run Holehe email registration check', [
238
+ `Target: ${email}`,
239
+ 'Checks 120+ platforms silently via forgotten-password flow',
240
+ 'Target is never alerted',
241
+ ]);
242
+ if (!approved)
243
+ throw new Error('Blocked Holehe scan. Ask user to approve.');
244
+ emitProgress({ type: 'tool:start', label: 'Holehe scanning', detail: email });
245
+ // --only-used suppresses negative results for cleaner output
246
+ // --no-color for clean parsing
247
+ const raw = await runTool('holehe', ['--only-used', '--no-color', email], 240_000);
248
+ // Parse: [+] = email used on platform
249
+ const found = [...raw.matchAll(/\[\+\]\s+([^\n]+)/g)].map((m) => m[1].trim());
250
+ // Parse: [x] = rate limited
251
+ const rateLimited = [...raw.matchAll(/\[x\]\s+([^\n]+)/g)].map((m) => m[1].trim());
252
+ const outFile = await saveOutput(`holehe/holehe-${email.replace('@', '_at_')}-${ts()}.txt`, raw);
253
+ emitProgress({ type: 'tool:end', label: 'Holehe complete', detail: `${found.length} platforms found` });
254
+ return { email, found, foundCount: found.length, rateLimited, outputFile: outFile, raw: raw.slice(0, 4000) };
255
+ },
256
+ }),
257
+ /**
258
+ * Epieos: epieos <email> [--api-key KEY]
259
+ * Reverse email → Google ID, profile pic, Calendar events, Maps reviews
260
+ * Docs: https://epieos.com
261
+ */
262
+ runEpieos: tool({
263
+ description: 'Reverse email lookup via Epieos. Extracts linked Google account ID, profile photo, public Calendar entries, and Google Maps reviews. Powerful for Gmail targets.',
264
+ inputSchema: z.object({
265
+ email: z.string().email(),
266
+ apiKey: z.string().optional().describe('Epieos API key for extended quota. Uses free tier if omitted.'),
267
+ }),
268
+ execute: async ({ email, apiKey }) => {
269
+ const approved = await confirmOsintAction('Run Epieos reverse email lookup', [
270
+ `Target: ${email}`,
271
+ 'Queries Epieos for Google ID, profile picture, Calendar, Maps reviews',
272
+ ]);
273
+ if (!approved)
274
+ throw new Error('Blocked Epieos lookup. Ask user to approve.');
275
+ emitProgress({ type: 'tool:start', label: 'Epieos lookup', detail: email });
276
+ const args = [email];
277
+ if (apiKey)
278
+ args.push('--api-key', apiKey);
279
+ const raw = await runTool('epieos', args, 60_000);
280
+ const outFile = await saveOutput(`epieos/epieos-${email.replace('@', '_at_')}-${ts()}.txt`, raw);
281
+ emitProgress({ type: 'tool:end', label: 'Epieos complete' });
282
+ return { email, outputFile: outFile, raw: raw.slice(0, 4000) };
283
+ },
284
+ }),
285
+ };
286
+ // ─── Phone Tools ──────────────────────────────────────────────────────────────
287
+ export const phoneTools = {
288
+ /**
289
+ * PhoneInfoga: phoneinfoga scan -n <number> [--disable scanner1,scanner2]
290
+ * Scanners: local, numverify (needs NUMVERIFY_API_KEY), googlesearch, ovh
291
+ * Exact flag: -n or --number (not --number as a positional)
292
+ * Docs: https://sundowndev.github.io/phoneinfoga/getting-started/usage/
293
+ */
294
+ runPhoneInfoga: tool({
295
+ description: 'Gather intelligence on a phone number using PhoneInfoga: carrier, country, line type, and OSINT footprints. Set NUMVERIFY_API_KEY env var for extended carrier data.',
296
+ inputSchema: z.object({
297
+ phoneNumber: z
298
+ .string()
299
+ .describe('Phone number with country code in E.164 format, e.g. +12125551234 or +233201234567.'),
300
+ disableScanners: z
301
+ .array(z.enum(['numverify', 'googlesearch', 'ovh']))
302
+ .optional()
303
+ .describe('Scanners to skip. "numverify" requires API key; skip if key not set.'),
304
+ }),
305
+ execute: async ({ phoneNumber, disableScanners }) => {
306
+ const approved = await confirmOsintAction('Run PhoneInfoga phone scan', [
307
+ `Target: ${phoneNumber}`,
308
+ 'Gathers carrier, line type, and OSINT footprints',
309
+ disableScanners?.length ? `Skipping scanners: ${disableScanners.join(', ')}` : 'Running all configured scanners',
310
+ ]);
311
+ if (!approved)
312
+ throw new Error('Blocked PhoneInfoga scan. Ask user to approve.');
313
+ emitProgress({ type: 'tool:start', label: 'PhoneInfoga scanning', detail: phoneNumber });
314
+ // Correct flag is: phoneinfoga scan -n "+12125551234"
315
+ const args = ['scan', '-n', phoneNumber];
316
+ if (disableScanners?.length) {
317
+ for (const s of disableScanners)
318
+ args.push('--disable', s);
319
+ }
320
+ const raw = await runTool('phoneinfoga', args, 120_000);
321
+ const outFile = await saveOutput(`phoneinfoga/phoneinfoga-${phoneNumber.replace(/\D/g, '')}-${ts()}.txt`, raw);
322
+ emitProgress({ type: 'tool:end', label: 'PhoneInfoga complete' });
323
+ return { phoneNumber, outputFile: outFile, raw: raw.slice(0, 4000) };
324
+ },
325
+ }),
326
+ };
327
+ // ─── Domain & Recon Tools ─────────────────────────────────────────────────────
328
+ export const domainTools = {
329
+ /**
330
+ * theHarvester: theHarvester -d <domain> -b <sources> -l <limit> [-f <output>] [-v] [-c] [-n]
331
+ * Sources: google, bing, yahoo, duckduckgo, crtsh, dnsdumpster, hackertarget,
332
+ * hunter, securityTrails, shodan, virustotal, certspotter, github-code, linkedin, all
333
+ * -v verify hosts via DNS
334
+ * -c DNS brute force
335
+ * -n DNS reverse query on ranges
336
+ * Docs: https://github.com/laramies/theHarvester
337
+ */
338
+ runTheHarvester: tool({
339
+ description: 'Harvest emails, subdomains, hosts, and employee names for a target domain. Queries search engines, cert databases, and DNS. API keys for Shodan/Hunter/SecurityTrails unlock more results.',
340
+ inputSchema: z.object({
341
+ domain: z.string().min(3).describe('Target domain, e.g. example.com.'),
342
+ sources: z
343
+ .array(z.enum([
344
+ 'baidu', 'bing', 'certspotter', 'crtsh', 'dnsdumpster', 'duckduckgo',
345
+ 'github-code', 'google', 'hackertarget', 'hunter', 'linkedin',
346
+ 'otx', 'securityTrails', 'shodan', 'urlscan', 'virustotal', 'yahoo', 'all',
347
+ ]))
348
+ .optional()
349
+ .default(['bing', 'crtsh', 'dnsdumpster', 'hackertarget', 'duckduckgo'])
350
+ .describe('Data sources to query. Use "all" to query every available source.'),
351
+ limit: z.number().int().min(10).max(1000).optional().default(200).describe('Max results per source.'),
352
+ verifyDns: z.boolean().optional().default(false).describe('Verify discovered hosts via DNS (-v flag).'),
353
+ bruteForceDns: z.boolean().optional().default(false).describe('DNS brute force for subdomains (-c flag).'),
354
+ }),
355
+ execute: async ({ domain, sources, limit, verifyDns, bruteForceDns }) => {
356
+ const approved = await confirmOsintAction('Run theHarvester domain recon', [
357
+ `Domain: ${domain}`,
358
+ `Sources: ${sources.join(', ')}`,
359
+ `Limit: ${limit} results per source`,
360
+ verifyDns ? 'DNS verification enabled' : '',
361
+ bruteForceDns ? 'DNS brute force enabled' : '',
362
+ ].filter(Boolean));
363
+ if (!approved)
364
+ throw new Error('Blocked theHarvester scan. Ask user to approve.');
365
+ emitProgress({ type: 'tool:start', label: 'theHarvester scanning', detail: domain });
366
+ const dir = await ensureOutputDir('harvester');
367
+ const outBase = path.join(dir, `${sanitize(domain)}-${ts()}`);
368
+ const args = [
369
+ '-d', domain,
370
+ '-b', sources.join(','),
371
+ '-l', String(limit),
372
+ '-f', outBase, // saves both .xml and .json
373
+ ];
374
+ if (verifyDns)
375
+ args.push('-v');
376
+ if (bruteForceDns)
377
+ args.push('-c');
378
+ const raw = await runTool('theHarvester', args, 600_000);
379
+ // Extract emails from raw output
380
+ const emails = [...new Set([...raw.matchAll(/[\w.+%-]+@[\w-]+\.[a-z]{2,}/gi)].map((m) => m[0].toLowerCase()))];
381
+ // Extract subdomains/hosts
382
+ const hosts = [...new Set([...raw.matchAll(/(?:\[\*\]|\[\+\])\s+([\w.-]+\.[\w.-]+)/g)].map((m) => m[1]))];
383
+ emitProgress({ type: 'tool:end', label: 'theHarvester complete', detail: `${emails.length} emails, ${hosts.length} hosts` });
384
+ return {
385
+ domain,
386
+ emails,
387
+ hosts,
388
+ emailCount: emails.length,
389
+ hostCount: hosts.length,
390
+ outputFiles: { xml: `${outBase}.xml`, json: `${outBase}.json` },
391
+ raw: raw.slice(0, 4000),
392
+ };
393
+ },
394
+ }),
395
+ /**
396
+ * SpiderFoot: python3 -m spiderfoot -s <target> -o json -R <output>
397
+ * Or spiderfoot CLI if installed globally
398
+ * Docs: https://github.com/smicallef/spiderfoot
399
+ */
400
+ runSpiderFoot: tool({
401
+ description: 'Full digital footprint mapping using SpiderFoot (200+ modules). Accepts IP, domain, email, username, or name as input. Queries WHOIS, DNS, leaks, social, certificates, and more.',
402
+ inputSchema: z.object({
403
+ target: z.string().min(1).describe('Target: domain, IP, email, username, or name.'),
404
+ modules: z
405
+ .array(z.string())
406
+ .optional()
407
+ .describe('Specific SpiderFoot module names (e.g. "sfp_whois", "sfp_dns_resolve"). Omit to auto-select by target type.'),
408
+ maxRuntime: z.number().int().min(30).max(3600).optional().default(300).describe('Max scan seconds (default 5 min).'),
409
+ }),
410
+ execute: async ({ target, modules, maxRuntime }) => {
411
+ const approved = await confirmOsintAction('Run SpiderFoot reconnaissance', [
412
+ `Target: ${target}`,
413
+ modules?.length ? `Modules: ${modules.join(', ')}` : 'Auto-selecting modules by target type',
414
+ `Max runtime: ${maxRuntime}s`,
415
+ ]);
416
+ if (!approved)
417
+ throw new Error('Blocked SpiderFoot scan. Ask user to approve.');
418
+ emitProgress({ type: 'tool:start', label: 'SpiderFoot scanning', detail: target });
419
+ const dir = await ensureOutputDir('spiderfoot');
420
+ const outFile = path.join(dir, `sf-${sanitize(target)}-${ts()}.json`);
421
+ const args = ['-s', target, '-o', 'json', '-R', outFile];
422
+ if (modules?.length)
423
+ args.push('-m', modules.join(','));
424
+ const raw = await runTool('spiderfoot', args, (maxRuntime + 30) * 1000);
425
+ emitProgress({ type: 'tool:end', label: 'SpiderFoot complete', detail: target });
426
+ return { target, outputFile: outFile, raw: raw.slice(0, 4000) };
427
+ },
428
+ }),
429
+ };
430
+ // ─── File & Network Tools ─────────────────────────────────────────────────────
431
+ export const forensicsTools = {
432
+ /**
433
+ * ExifTool: exiftool [-json] [-csv] [-GPS*] <file>
434
+ * -json structured JSON output (one object per file)
435
+ * -GPS* extract only GPS fields
436
+ * -fast skip tail-of-file scan (faster on large files)
437
+ * Docs: https://exiftool.org
438
+ */
439
+ runExifTool: tool({
440
+ description: 'Extract all hidden metadata from an image, PDF, or document using ExifTool. Can reveal GPS coordinates, camera make/model/serial, author, creation timestamps, and software used.',
441
+ inputSchema: z.object({
442
+ filePath: z.string().min(1).describe('Absolute path to the file to analyze.'),
443
+ gpsOnly: z.boolean().optional().default(false).describe('Extract only GPS fields — faster if you just need location data.'),
444
+ }),
445
+ execute: async ({ filePath, gpsOnly }) => {
446
+ const approved = await confirmOsintAction('Run ExifTool metadata extraction', [
447
+ `File: ${filePath}`,
448
+ gpsOnly ? 'Extracting GPS coordinates only' : 'Extracting all available metadata',
449
+ ]);
450
+ if (!approved)
451
+ throw new Error('Blocked ExifTool analysis. Ask user to approve.');
452
+ if (!existsSync(filePath))
453
+ throw new Error(`File not found: ${filePath}`);
454
+ emitProgress({ type: 'tool:start', label: 'ExifTool extracting metadata', detail: filePath });
455
+ const args = ['-json'];
456
+ if (gpsOnly)
457
+ args.push('-GPS*');
458
+ args.push(filePath);
459
+ const raw = await runTool('exiftool', args, 30_000);
460
+ let metadata = null;
461
+ try {
462
+ const arr = JSON.parse(raw);
463
+ metadata = arr[0] ?? null;
464
+ }
465
+ catch { /* output wasn't valid JSON */ }
466
+ const outFile = await saveOutput(`exiftool/exiftool-${sanitize(path.basename(filePath))}-${ts()}.json`, raw);
467
+ emitProgress({ type: 'tool:end', label: 'ExifTool complete' });
468
+ return { filePath, metadata, outputFile: outFile };
469
+ },
470
+ }),
471
+ /**
472
+ * Shodan CLI: shodan host <ip> | shodan domain <domain> | shodan search <query>
473
+ * Requires SHODAN_API_KEY env var (free tier works for basic host lookups)
474
+ * Docs: https://cli.shodan.io
475
+ */
476
+ runShodan: tool({
477
+ description: 'Query Shodan for open ports, exposed services, CVEs, and device banners on an IP or domain. Requires SHODAN_API_KEY environment variable. Free tier supports basic host lookups.',
478
+ inputSchema: z.object({
479
+ target: z.string().min(1).describe('IP address or domain to look up.'),
480
+ type: z
481
+ .enum(['host', 'domain', 'search'])
482
+ .optional()
483
+ .default('host')
484
+ .describe('"host" for a specific IP, "domain" for DNS records/IPs, "search" for a Shodan dork query.'),
485
+ query: z.string().optional().describe('Shodan search query when type is "search", e.g. "org:\\"Example Corp\\"".'),
486
+ }),
487
+ execute: async ({ target, type, query }) => {
488
+ if (!process.env.SHODAN_API_KEY) {
489
+ throw new Error('SHODAN_API_KEY is not set. Add it to your .env file and restart the agent.');
490
+ }
491
+ const approved = await confirmOsintAction('Run Shodan lookup', [
492
+ `Target: ${target}`,
493
+ `Type: ${type}`,
494
+ 'Queries Shodan for open ports, banners, CVEs',
495
+ ]);
496
+ if (!approved)
497
+ throw new Error('Blocked Shodan lookup. Ask user to approve.');
498
+ emitProgress({ type: 'tool:start', label: 'Shodan querying', detail: target });
499
+ let args;
500
+ if (type === 'search') {
501
+ // shodan search --fields ip_str,port,org,hostnames <query>
502
+ args = ['search', '--fields', 'ip_str,port,org,hostnames', query ?? target];
503
+ }
504
+ else if (type === 'domain') {
505
+ args = ['domain', target];
506
+ }
507
+ else {
508
+ args = ['host', target];
509
+ }
510
+ const raw = await runTool('shodan', args, 60_000);
511
+ const outFile = await saveOutput(`shodan/shodan-${sanitize(target)}-${ts()}.txt`, raw);
512
+ emitProgress({ type: 'tool:end', label: 'Shodan complete' });
513
+ return { target, type, outputFile: outFile, raw: raw.slice(0, 4000) };
514
+ },
515
+ }),
516
+ };
517
+ // ─── Meta Orchestration ───────────────────────────────────────────────────────
518
+ export const orchestrationTools = {
519
+ /**
520
+ * Master investigation entry point — chains the right tools based on what identifiers are known.
521
+ *
522
+ * Chain logic by depth:
523
+ * quick → Sherlock, Holehe, PhoneInfoga, theHarvester (fastest single-source per input type)
524
+ * standard → all quick + Blackbird, ExifTool
525
+ * deep → all standard + Maigret, Linkook, SpiderFoot (thorough, slow)
526
+ */
527
+ osintInvestigation: tool({
528
+ description: 'Master OSINT investigation. Accepts any combination of identifiers (username, email, phone, domain, file, profile URL) and chains the right tools automatically. Use this as the primary entry point.',
529
+ inputSchema: z.object({
530
+ username: z.string().optional(),
531
+ email: z.string().email().optional(),
532
+ phone: z.string().optional().describe('E.164 format, e.g. +233201234567'),
533
+ domain: z.string().optional().describe('e.g. example.com'),
534
+ filePath: z.string().optional().describe('Absolute path to image/PDF for metadata extraction.'),
535
+ profileUrl: z.string().url().optional().describe('A known profile URL to start recursive link mapping from.'),
536
+ depth: z
537
+ .enum(['quick', 'standard', 'deep'])
538
+ .optional()
539
+ .default('standard')
540
+ .describe('quick = one fast tool per input; standard = all reliable tools; deep = everything including Maigret, SpiderFoot, Linkook (slow)'),
541
+ }),
542
+ execute: async ({ username, email, phone, domain, filePath, profileUrl, depth }) => {
543
+ const identifiers = [
544
+ username && `username: ${username}`,
545
+ email && `email: ${email}`,
546
+ phone && `phone: ${phone}`,
547
+ domain && `domain: ${domain}`,
548
+ filePath && `file: ${filePath}`,
549
+ profileUrl && `profileUrl: ${profileUrl}`,
550
+ ].filter(Boolean);
551
+ if (!identifiers.length)
552
+ throw new Error('Provide at least one identifier.');
553
+ const approved = await confirmOsintAction('Run full OSINT investigation', [
554
+ `Identifiers: ${identifiers.join(', ')}`,
555
+ `Depth: ${depth}`,
556
+ 'Chains multiple OSINT tools sequentially based on available inputs',
557
+ ]);
558
+ if (!approved)
559
+ throw new Error('Blocked investigation. Ask user to approve.');
560
+ // Build execution plan
561
+ const plan = [];
562
+ if (username) {
563
+ plan.push({ name: 'sherlock', run: () => runTool('sherlock', ['--print-found', '--no-color', '--timeout', '30', username], 90_000) });
564
+ if (depth === 'deep') {
565
+ plan.push({ name: 'maigret', run: () => runTool('maigret', ['--no-color', '--json', username], 600_000) });
566
+ }
567
+ if (depth !== 'quick') {
568
+ plan.push({
569
+ name: 'blackbird',
570
+ run: () => runTool('blackbird', ['--no-nsfw', '-u', username], 240_000),
571
+ });
572
+ }
573
+ if (depth === 'deep' && profileUrl) {
574
+ plan.push({ name: 'linkook', run: () => runTool('linkook', ['--url', profileUrl, '--depth', '2'], 300_000) });
575
+ }
576
+ }
577
+ if (email) {
578
+ plan.push({ name: 'holehe', run: () => runTool('holehe', ['--only-used', '--no-color', email], 240_000) });
579
+ if (depth !== 'quick') {
580
+ plan.push({ name: 'epieos', run: () => runTool('epieos', [email], 60_000) });
581
+ plan.push({ name: 'blackbird (email)', run: () => runTool('blackbird', ['--no-nsfw', '-e', email], 240_000) });
582
+ }
583
+ }
584
+ if (phone) {
585
+ plan.push({ name: 'phoneinfoga', run: () => runTool('phoneinfoga', ['scan', '-n', phone], 120_000) });
586
+ }
587
+ if (domain) {
588
+ plan.push({
589
+ name: 'theHarvester',
590
+ run: () => runTool('theHarvester', ['-d', domain, '-b', 'bing,crtsh,dnsdumpster,hackertarget', '-l', '200'], 600_000),
591
+ });
592
+ if (depth === 'deep') {
593
+ plan.push({ name: 'spiderfoot', run: () => runTool('spiderfoot', ['-s', domain, '-o', 'json'], 600_000) });
594
+ plan.push({ name: 'shodan', run: () => runTool('shodan', ['domain', domain], 60_000) });
595
+ }
596
+ }
597
+ if (filePath && existsSync(filePath)) {
598
+ plan.push({ name: 'exiftool', run: () => runTool('exiftool', ['-json', filePath], 30_000) });
599
+ }
600
+ emitProgress({ type: 'tool:start', label: 'Investigation started', detail: `${plan.length} tools planned` });
601
+ const results = {};
602
+ for (const step of plan) {
603
+ emitProgress({ type: 'tool:start', label: `Running ${step.name}` });
604
+ try {
605
+ const raw = await step.run();
606
+ results[step.name] = raw.slice(0, 2000);
607
+ emitProgress({ type: 'tool:end', label: `${step.name} done` });
608
+ }
609
+ catch (err) {
610
+ results[step.name] = { error: err instanceof Error ? err.message : String(err) };
611
+ emitProgress({ type: 'tool:end', label: `${step.name} failed` });
612
+ }
613
+ }
614
+ const report = JSON.stringify({ identifiers, depth, plan: plan.map((p) => p.name), results }, null, 2);
615
+ const outFile = await saveOutput(`investigation-${sanitize(identifiers[0])}-${ts()}.json`, report);
616
+ emitProgress({ type: 'tool:end', label: 'Investigation complete', detail: outFile });
617
+ return { identifiers, depth, toolsRun: plan.map((p) => p.name), outputFile: outFile, results };
618
+ },
619
+ }),
620
+ };
621
+ // ─── Barrel export ────────────────────────────────────────────────────────────
622
+ export const osintTools = {
623
+ ...usernameTools,
624
+ ...emailTools,
625
+ ...phoneTools,
626
+ ...domainTools,
627
+ ...forensicsTools,
628
+ ...orchestrationTools,
629
+ };
630
+ //# sourceMappingURL=osint.tool.js.map