intelwatch 1.2.0 → 1.3.2
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/CHANGELOG-DRAFT.md +44 -0
- package/CHANGELOG.md +30 -0
- package/Endrix-Intelwatch-DueDil.pdf +0 -0
- package/RELEASE.md +15 -0
- package/export.pdf +0 -0
- package/package.json +3 -2
- package/profile-480254275.pdf +0 -0
- package/profile-775726417.pdf +0 -0
- package/profile-794598813.pdf +0 -0
- package/src/ai/client.js +39 -1
- package/src/commands/profile.js +58 -48
- package/src/commands/report.js +11 -13
- package/src/index.js +30 -4
- package/src/license.js +194 -0
- package/src/providers/apollo.js +172 -0
- package/src/providers/clearbit.js +136 -0
- package/src/providers/index.js +30 -0
- package/src/providers/opencorporates.js +159 -0
- package/src/providers/pappers.js +75 -0
- package/src/providers/registry.js +531 -0
- package/src/scrapers/reddit-hn.js +161 -0
- package/src/trackers/brand.js +66 -3
- package/src/trackers/competitor.js +9 -10
- package/src/utils/error-handler.js +10 -0
- package/src/utils/export.js +221 -99
package/src/utils/export.js
CHANGED
|
@@ -1,124 +1,200 @@
|
|
|
1
1
|
import { writeFileSync } from 'fs';
|
|
2
|
-
import {
|
|
2
|
+
import { resolve } from 'path';
|
|
3
|
+
import * as XLSX from 'xlsx';
|
|
4
|
+
import { isPro, requirePro, getLimits, applyFreeLimit, printPaywallAndExit } from '../license.js';
|
|
5
|
+
|
|
6
|
+
// ── JSON Export ──────────────────────────────────────────────────────────────
|
|
3
7
|
|
|
4
8
|
/**
|
|
5
|
-
* Export data to JSON format
|
|
9
|
+
* Export data to JSON format.
|
|
10
|
+
* @param {any} data
|
|
11
|
+
* @param {string|null} outputPath
|
|
12
|
+
* @returns {string} Status message
|
|
6
13
|
*/
|
|
7
14
|
export function exportToJSON(data, outputPath = null) {
|
|
8
15
|
const jsonStr = JSON.stringify(data, null, 2);
|
|
9
|
-
|
|
16
|
+
|
|
10
17
|
if (outputPath) {
|
|
11
|
-
writeFileSync(outputPath, jsonStr, 'utf8');
|
|
18
|
+
writeFileSync(resolve(outputPath), jsonStr, 'utf8');
|
|
12
19
|
return `Exported to ${outputPath}`;
|
|
13
20
|
}
|
|
14
|
-
|
|
15
|
-
// Print to console if no path specified
|
|
21
|
+
|
|
16
22
|
console.log(jsonStr);
|
|
17
23
|
return 'JSON output printed to console';
|
|
18
24
|
}
|
|
19
25
|
|
|
26
|
+
// ── CSV Export ────────────────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Escape a value for CSV (RFC 4180 compliant).
|
|
30
|
+
* @param {any} value
|
|
31
|
+
* @returns {string}
|
|
32
|
+
*/
|
|
33
|
+
function escapeCSV(value) {
|
|
34
|
+
if (value == null) return '';
|
|
35
|
+
let str = typeof value === 'object'
|
|
36
|
+
? (Array.isArray(value) ? value.join('; ') : JSON.stringify(value))
|
|
37
|
+
: String(value);
|
|
38
|
+
// Always quote if contains comma, double-quote, newline, or leading/trailing whitespace
|
|
39
|
+
if (str.includes(',') || str.includes('"') || str.includes('\n') || str.includes('\r') || str !== str.trim()) {
|
|
40
|
+
str = `"${str.replace(/"/g, '""')}"`;
|
|
41
|
+
}
|
|
42
|
+
return str;
|
|
43
|
+
}
|
|
44
|
+
|
|
20
45
|
/**
|
|
21
|
-
* Export
|
|
22
|
-
*
|
|
46
|
+
* Export an array of objects to CSV.
|
|
47
|
+
* @param {Array<object>} data
|
|
48
|
+
* @param {string|null} outputPath
|
|
49
|
+
* @param {{ headers?: string[], separator?: string }} options
|
|
50
|
+
* @returns {string} Status message
|
|
23
51
|
*/
|
|
24
52
|
export function exportToCSV(data, outputPath = null, options = {}) {
|
|
25
53
|
if (!Array.isArray(data)) {
|
|
26
54
|
throw new Error('CSV export requires an array of objects');
|
|
27
55
|
}
|
|
28
|
-
|
|
56
|
+
|
|
57
|
+
const sep = options.separator || ',';
|
|
58
|
+
|
|
29
59
|
if (data.length === 0) {
|
|
30
|
-
const
|
|
60
|
+
const empty = options.headers ? options.headers.join(sep) + '\n' : '';
|
|
31
61
|
if (outputPath) {
|
|
32
|
-
writeFileSync(outputPath,
|
|
62
|
+
writeFileSync(resolve(outputPath), empty, 'utf8');
|
|
33
63
|
return `Empty CSV exported to ${outputPath}`;
|
|
34
64
|
}
|
|
35
|
-
console.log(
|
|
65
|
+
console.log(empty);
|
|
36
66
|
return 'Empty CSV output';
|
|
37
67
|
}
|
|
38
68
|
|
|
39
|
-
// Auto-detect headers from first object if not provided
|
|
40
69
|
const headers = options.headers || Object.keys(data[0]);
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const csvRows = [headers.join(',')];
|
|
44
|
-
|
|
45
|
-
// CSV data rows
|
|
70
|
+
const rows = [headers.join(sep)];
|
|
71
|
+
|
|
46
72
|
for (const item of data) {
|
|
47
|
-
const row = headers.map(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
// Handle nested objects/arrays
|
|
51
|
-
if (typeof value === 'object' && value !== null) {
|
|
52
|
-
if (Array.isArray(value)) {
|
|
53
|
-
value = value.join('; ');
|
|
54
|
-
} else {
|
|
55
|
-
value = JSON.stringify(value);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Escape CSV values
|
|
60
|
-
value = String(value || '');
|
|
61
|
-
if (value.includes(',') || value.includes('"') || value.includes('\n')) {
|
|
62
|
-
value = `"${value.replace(/"/g, '""')}"`;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return value;
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
csvRows.push(row.join(','));
|
|
73
|
+
const row = headers.map(h => escapeCSV(item[h]));
|
|
74
|
+
rows.push(row.join(sep));
|
|
69
75
|
}
|
|
70
|
-
|
|
71
|
-
const csvStr =
|
|
72
|
-
|
|
76
|
+
|
|
77
|
+
const csvStr = rows.join('\n') + '\n';
|
|
78
|
+
|
|
73
79
|
if (outputPath) {
|
|
74
|
-
writeFileSync(outputPath, csvStr, 'utf8');
|
|
80
|
+
writeFileSync(resolve(outputPath), csvStr, 'utf8');
|
|
75
81
|
return `CSV exported to ${outputPath}`;
|
|
76
82
|
}
|
|
77
|
-
|
|
83
|
+
|
|
78
84
|
console.log(csvStr);
|
|
79
85
|
return 'CSV output printed to console';
|
|
80
86
|
}
|
|
81
87
|
|
|
88
|
+
// ── XLS Export (xlsx format) ─────────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Export data to XLSX (Excel) format.
|
|
92
|
+
* @param {Array<object>} data — rows to export
|
|
93
|
+
* @param {string} outputPath — output file (.xlsx)
|
|
94
|
+
* @param {{ sheetName?: string, headers?: string[] }} options
|
|
95
|
+
* @returns {string} Status message
|
|
96
|
+
*/
|
|
97
|
+
export function exportToXLS(data, outputPath, options = {}) {
|
|
98
|
+
if (!outputPath) throw new Error('XLS export requires an output path');
|
|
99
|
+
if (!Array.isArray(data)) throw new Error('XLS export requires an array of objects');
|
|
100
|
+
|
|
101
|
+
const sheetName = options.sheetName || 'Data';
|
|
102
|
+
const headers = options.headers || (data.length > 0 ? Object.keys(data[0]) : []);
|
|
103
|
+
|
|
104
|
+
// Build worksheet from array of arrays (header row + data rows)
|
|
105
|
+
const aoa = [headers];
|
|
106
|
+
for (const item of data) {
|
|
107
|
+
aoa.push(headers.map(h => {
|
|
108
|
+
const v = item[h];
|
|
109
|
+
if (v == null) return '';
|
|
110
|
+
if (Array.isArray(v)) return v.join('; ');
|
|
111
|
+
if (typeof v === 'object') return JSON.stringify(v);
|
|
112
|
+
return v;
|
|
113
|
+
}));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const wb = XLSX.utils.book_new();
|
|
117
|
+
const ws = XLSX.utils.aoa_to_sheet(aoa);
|
|
118
|
+
|
|
119
|
+
// Auto-size columns (approximate)
|
|
120
|
+
ws['!cols'] = headers.map((h, i) => {
|
|
121
|
+
let maxLen = h.length;
|
|
122
|
+
for (const row of aoa.slice(1)) {
|
|
123
|
+
const cellLen = String(row[i] || '').length;
|
|
124
|
+
if (cellLen > maxLen) maxLen = cellLen;
|
|
125
|
+
}
|
|
126
|
+
return { wch: Math.min(maxLen + 2, 60) };
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
XLSX.utils.book_append_sheet(wb, ws, sheetName);
|
|
130
|
+
XLSX.writeFile(wb, resolve(outputPath));
|
|
131
|
+
return `XLS exported to ${outputPath}`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ── PDF Export (via @recognity/pdf-report) ────────────────────────────────────
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Export data to a PDF report.
|
|
138
|
+
* @param {object} data — structured report data
|
|
139
|
+
* @param {string} outputPath — output file (.pdf)
|
|
140
|
+
* @param {{ type?: string, title?: string, branding?: object }} options
|
|
141
|
+
* @returns {Promise<string>} Status message
|
|
142
|
+
*/
|
|
143
|
+
export async function exportToPDF(data, outputPath, options = {}) {
|
|
144
|
+
if (!outputPath) throw new Error('PDF export requires an output path');
|
|
145
|
+
|
|
146
|
+
const { generatePDF } = await import('@recognity/pdf-report');
|
|
147
|
+
await generatePDF({
|
|
148
|
+
type: options.type || 'intel-report',
|
|
149
|
+
title: options.title || 'IntelWatch Report',
|
|
150
|
+
data,
|
|
151
|
+
output: resolve(outputPath),
|
|
152
|
+
branding: options.branding,
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
return `PDF exported to ${outputPath}`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ── Flatten Object ───────────────────────────────────────────────────────────
|
|
159
|
+
|
|
82
160
|
/**
|
|
83
|
-
* Flatten nested objects for
|
|
84
|
-
*
|
|
161
|
+
* Flatten nested objects for tabular export.
|
|
162
|
+
* { user: { name: 'Jo' } } → { 'user.name': 'Jo' }
|
|
85
163
|
*/
|
|
86
164
|
export function flattenObject(obj, prefix = '', result = {}) {
|
|
87
|
-
for (const key
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
result[newKey] = obj[key];
|
|
95
|
-
}
|
|
165
|
+
for (const key of Object.keys(obj)) {
|
|
166
|
+
const newKey = prefix ? `${prefix}.${key}` : key;
|
|
167
|
+
const val = obj[key];
|
|
168
|
+
if (val && typeof val === 'object' && !Array.isArray(val) && !(val instanceof Date)) {
|
|
169
|
+
flattenObject(val, newKey, result);
|
|
170
|
+
} else {
|
|
171
|
+
result[newKey] = val;
|
|
96
172
|
}
|
|
97
173
|
}
|
|
98
174
|
return result;
|
|
99
175
|
}
|
|
100
176
|
|
|
177
|
+
// ── Format for Export ────────────────────────────────────────────────────────
|
|
178
|
+
|
|
101
179
|
/**
|
|
102
|
-
* Format data for export based on command type
|
|
180
|
+
* Format data for export based on command type.
|
|
181
|
+
* @param {any} data
|
|
182
|
+
* @param {string} commandType
|
|
183
|
+
* @returns {Array<object>}
|
|
103
184
|
*/
|
|
104
185
|
export function formatForExport(data, commandType) {
|
|
105
186
|
switch (commandType) {
|
|
106
|
-
case 'check':
|
|
107
|
-
|
|
108
|
-
case '
|
|
109
|
-
|
|
110
|
-
case '
|
|
111
|
-
|
|
112
|
-
case 'profile':
|
|
113
|
-
return formatProfileData(data);
|
|
114
|
-
default:
|
|
115
|
-
return data;
|
|
187
|
+
case 'check': return formatCheckData(data);
|
|
188
|
+
case 'digest': return formatDigestData(data);
|
|
189
|
+
case 'report': return formatReportData(data);
|
|
190
|
+
case 'profile': return formatProfileData(data);
|
|
191
|
+
case 'discover': return formatDiscoverData(data);
|
|
192
|
+
default: return Array.isArray(data) ? data : [data];
|
|
116
193
|
}
|
|
117
194
|
}
|
|
118
195
|
|
|
119
196
|
function formatCheckData(data) {
|
|
120
197
|
if (!Array.isArray(data)) return [data];
|
|
121
|
-
|
|
122
198
|
return data.map(item => ({
|
|
123
199
|
trackerId: item.id || item.trackerId,
|
|
124
200
|
name: item.name,
|
|
@@ -129,41 +205,32 @@ function formatCheckData(data) {
|
|
|
129
205
|
changes: Array.isArray(item.changes) ? item.changes.length : 0,
|
|
130
206
|
techStack: Array.isArray(item.techStack) ? item.techStack.join('; ') : '',
|
|
131
207
|
seoScore: item.seoScore || null,
|
|
132
|
-
sentiment: item.sentiment || null
|
|
208
|
+
sentiment: item.sentiment || null,
|
|
133
209
|
}));
|
|
134
210
|
}
|
|
135
211
|
|
|
136
212
|
function formatDigestData(data) {
|
|
137
213
|
if (!Array.isArray(data)) return [data];
|
|
138
|
-
|
|
139
214
|
return data.map(item => flattenObject({
|
|
140
|
-
tracker: {
|
|
141
|
-
id: item.trackerId,
|
|
142
|
-
name: item.name,
|
|
143
|
-
type: item.type
|
|
144
|
-
},
|
|
215
|
+
tracker: { id: item.trackerId, name: item.name, type: item.type },
|
|
145
216
|
changes: {
|
|
146
217
|
total: item.changes?.length || 0,
|
|
147
218
|
critical: item.changes?.filter(c => c.severity === 'critical').length || 0,
|
|
148
219
|
major: item.changes?.filter(c => c.severity === 'major').length || 0,
|
|
149
|
-
minor: item.changes?.filter(c => c.severity === 'minor').length || 0
|
|
220
|
+
minor: item.changes?.filter(c => c.severity === 'minor').length || 0,
|
|
150
221
|
},
|
|
151
|
-
summary: item.summary || ''
|
|
222
|
+
summary: item.summary || '',
|
|
152
223
|
}));
|
|
153
224
|
}
|
|
154
225
|
|
|
155
226
|
function formatReportData(data) {
|
|
156
|
-
// For reports, export the raw data structure
|
|
157
227
|
return Array.isArray(data) ? data : [data];
|
|
158
228
|
}
|
|
159
229
|
|
|
160
230
|
function formatProfileData(data) {
|
|
161
231
|
if (!data) return [];
|
|
162
|
-
|
|
163
232
|
const profile = Array.isArray(data) ? data[0] : data;
|
|
164
|
-
|
|
165
|
-
const flattened = {
|
|
166
|
-
// Company identity
|
|
233
|
+
return [{
|
|
167
234
|
siren: profile.siren,
|
|
168
235
|
name: profile.name || profile.identity?.name,
|
|
169
236
|
legalForm: profile.identity?.formeJuridique,
|
|
@@ -171,31 +238,86 @@ function formatProfileData(data) {
|
|
|
171
238
|
nafLabel: profile.identity?.nafLabel,
|
|
172
239
|
creationDate: profile.identity?.dateCreation,
|
|
173
240
|
address: profile.identity?.adresse,
|
|
174
|
-
|
|
175
|
-
// Financial data (latest year)
|
|
176
241
|
revenue: profile.financialHistory?.[0]?.revenue,
|
|
177
242
|
netIncome: profile.financialHistory?.[0]?.netIncome,
|
|
178
243
|
employees: profile.financialHistory?.[0]?.employees,
|
|
179
244
|
year: profile.financialHistory?.[0]?.year,
|
|
180
|
-
|
|
181
|
-
// AI analysis
|
|
182
245
|
executiveSummary: profile.executiveSummary,
|
|
183
246
|
healthScore: profile.healthScore?.score,
|
|
184
247
|
riskLevel: profile.riskAssessment?.overall,
|
|
185
|
-
|
|
186
|
-
// Strengths (concatenated)
|
|
187
248
|
strengths: profile.strengths?.map(s => s.text || s).join('; ') || '',
|
|
188
|
-
|
|
189
|
-
// Weaknesses (concatenated)
|
|
190
249
|
weaknesses: profile.weaknesses?.map(w => w.text || w).join('; ') || '',
|
|
191
|
-
|
|
192
|
-
// Competitors (concatenated)
|
|
193
250
|
competitors: profile.competitors?.map(c => c.name).join('; ') || '',
|
|
194
|
-
|
|
195
|
-
// Group info
|
|
196
251
|
subsidiariesCount: profile.subsidiaries?.length || 0,
|
|
197
|
-
groupRevenue: profile.groupStructure?.consolidatedRevenue
|
|
198
|
-
};
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
252
|
+
groupRevenue: profile.groupStructure?.consolidatedRevenue,
|
|
253
|
+
}];
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function formatDiscoverData(data) {
|
|
257
|
+
if (!Array.isArray(data)) return [data];
|
|
258
|
+
return data.map(item => ({
|
|
259
|
+
name: item.name || item.domain,
|
|
260
|
+
domain: item.domain,
|
|
261
|
+
url: item.url,
|
|
262
|
+
relevanceScore: item.relevanceScore || item.score,
|
|
263
|
+
description: item.description || item.snippet || '',
|
|
264
|
+
category: item.category || '',
|
|
265
|
+
}));
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// ── Unified Export Handler ───────────────────────────────────────────────────
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Handle --export flag for any command.
|
|
272
|
+
*
|
|
273
|
+
* Free tier: json, csv (capped at 50 rows)
|
|
274
|
+
* Pro tier: json, csv (unlimited), xls, pdf
|
|
275
|
+
*
|
|
276
|
+
* @param {string} format — 'json' | 'csv' | 'xls' | 'pdf'
|
|
277
|
+
* @param {any} data — data to export
|
|
278
|
+
* @param {{ output?: string, commandType?: string, pdfOptions?: object }} options
|
|
279
|
+
* @returns {Promise<string>} Status message
|
|
280
|
+
*/
|
|
281
|
+
export async function handleExport(format, data, options = {}) {
|
|
282
|
+
const fmt = format.toLowerCase();
|
|
283
|
+
const limits = getLimits();
|
|
284
|
+
|
|
285
|
+
// Gate Pro-only formats — clean paywall exit
|
|
286
|
+
if (['xls', 'xlsx', 'excel', 'pdf'].includes(fmt)) {
|
|
287
|
+
if (!isPro()) {
|
|
288
|
+
printPaywallAndExit(`Export to ${fmt.toUpperCase()}`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
let formatted = options.commandType
|
|
293
|
+
? formatForExport(data, options.commandType)
|
|
294
|
+
: (Array.isArray(data) ? data : [data]);
|
|
295
|
+
|
|
296
|
+
// Apply Free-tier row cap on CSV
|
|
297
|
+
if (fmt === 'csv' && Array.isArray(formatted)) {
|
|
298
|
+
formatted = applyFreeLimit(formatted, limits.csvMaxRows, 'CSV export rows');
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
switch (fmt) {
|
|
302
|
+
case 'json': {
|
|
303
|
+
const outPath = options.output?.replace(/\.[^.]+$/, '.json') || null;
|
|
304
|
+
return exportToJSON(formatted, outPath);
|
|
305
|
+
}
|
|
306
|
+
case 'csv': {
|
|
307
|
+
const outPath = options.output?.replace(/\.[^.]+$/, '.csv') || null;
|
|
308
|
+
return exportToCSV(formatted, outPath);
|
|
309
|
+
}
|
|
310
|
+
case 'xls':
|
|
311
|
+
case 'xlsx':
|
|
312
|
+
case 'excel': {
|
|
313
|
+
const outPath = options.output?.replace(/\.[^.]+$/, '.xlsx') || 'export.xlsx';
|
|
314
|
+
return exportToXLS(formatted, outPath);
|
|
315
|
+
}
|
|
316
|
+
case 'pdf': {
|
|
317
|
+
const outPath = options.output?.replace(/\.[^.]+$/, '.pdf') || 'export.pdf';
|
|
318
|
+
return exportToPDF(options.pdfData || data, outPath, options.pdfOptions || {});
|
|
319
|
+
}
|
|
320
|
+
default:
|
|
321
|
+
throw new Error(`Unsupported export format: ${format}. Use json, csv, xls, or pdf.`);
|
|
322
|
+
}
|
|
323
|
+
}
|