intelwatch 1.0.0 → 1.1.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.
- package/LICENSE +21 -0
- package/package.json +1 -1
- package/src/commands/profile.js +329 -0
- package/src/index.js +12 -0
- package/src/scrapers/pappers.js +99 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Recognity
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/package.json
CHANGED
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import Table from 'cli-table3';
|
|
3
|
+
import { pappersGetFullDossier, pappersSearchByName } from '../scrapers/pappers.js';
|
|
4
|
+
import { searchPressMentions } from '../scrapers/brave-search.js';
|
|
5
|
+
import { analyzeSite } from '../scrapers/site-analyzer.js';
|
|
6
|
+
import { callAI, hasAIKey } from '../ai/client.js';
|
|
7
|
+
import { header, section, warn, error } from '../utils/display.js';
|
|
8
|
+
|
|
9
|
+
const LICENSE_URL = 'https://recognity.fr/tools/intelwatch';
|
|
10
|
+
|
|
11
|
+
export async function runMA(sirenOrName, options) {
|
|
12
|
+
const hasLicense = !!process.env.INTELWATCH_LICENSE_KEY;
|
|
13
|
+
const isPreview = !!options.preview;
|
|
14
|
+
|
|
15
|
+
// ── License gate ───────────────────────────────────────────────────────────
|
|
16
|
+
if (!hasLicense && !isPreview) {
|
|
17
|
+
console.log(chalk.yellow.bold('\n ⚡ Deep Profile Due Diligence — Module Premium\n'));
|
|
18
|
+
console.log(chalk.red(' The Deep Profile requires an Intelwatch Deep Profile license.'));
|
|
19
|
+
console.log(chalk.gray(` Get yours at ${LICENSE_URL}\n`));
|
|
20
|
+
console.log(chalk.gray(' Run with --preview for a limited preview (company identity + last year financials only).'));
|
|
21
|
+
console.log('');
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (isPreview && !hasLicense) {
|
|
26
|
+
console.log(chalk.yellow(' ⚡ PREVIEW MODE — Company identity + last year financials only'));
|
|
27
|
+
console.log(chalk.gray(` Upgrade to Intelwatch Deep Profile for full due diligence: ${LICENSE_URL}\n`));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ── SIREN or name lookup ───────────────────────────────────────────────────
|
|
31
|
+
let siren = sirenOrName;
|
|
32
|
+
|
|
33
|
+
if (!/^\d{9}$/.test(sirenOrName)) {
|
|
34
|
+
console.log(chalk.gray(` Searching for: "${sirenOrName}"...`));
|
|
35
|
+
const { results, error: searchErr } = await pappersSearchByName(sirenOrName, { count: 1 });
|
|
36
|
+
if (searchErr || !results.length) {
|
|
37
|
+
error(`Company not found: ${searchErr || 'No results'}`);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
siren = results[0].siren;
|
|
41
|
+
const foundName = results[0].nom_entreprise || results[0].denomination;
|
|
42
|
+
console.log(chalk.gray(` Found: ${foundName} (SIREN: ${siren})`));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ── Fetch full dossier ─────────────────────────────────────────────────────
|
|
46
|
+
console.log(chalk.gray(' Fetching dossier from Pappers...'));
|
|
47
|
+
const { data, error: dossierErr } = await pappersGetFullDossier(siren);
|
|
48
|
+
|
|
49
|
+
if (dossierErr || !data) {
|
|
50
|
+
error(`Failed to fetch dossier: ${dossierErr || 'Unknown error'}`);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const { identity, financialHistory, ubo, bodacc, dirigeants, proceduresCollectives } = data;
|
|
55
|
+
|
|
56
|
+
// ── Header ─────────────────────────────────────────────────────────────────
|
|
57
|
+
header(`🏢 Due Diligence Deep Profile — ${identity.name || siren}`);
|
|
58
|
+
|
|
59
|
+
// ── Company Identity ───────────────────────────────────────────────────────
|
|
60
|
+
section(' 📋 Identité');
|
|
61
|
+
const statusColor = identity.status === 'Actif' ? chalk.green : chalk.red;
|
|
62
|
+
printRow('Nom', identity.name);
|
|
63
|
+
printRow('SIREN', identity.siren);
|
|
64
|
+
printRow('SIRET siège', identity.siret);
|
|
65
|
+
printRow('Forme juridique', identity.formeJuridique);
|
|
66
|
+
printRow('Capital', identity.capital != null ? `${formatEuro(identity.capital)} ${identity.capitalMonnaie}` : null);
|
|
67
|
+
printRow('NAF', identity.nafCode ? `${identity.nafCode} — ${identity.nafLabel}` : null);
|
|
68
|
+
printRow('Création', identity.dateCreation);
|
|
69
|
+
printRow('Statut', identity.status, statusColor(identity.status));
|
|
70
|
+
printRow('Effectifs', identity.effectifs);
|
|
71
|
+
printRow('Adresse', [identity.adresse, identity.codePostal, identity.ville].filter(Boolean).join(' ') || null);
|
|
72
|
+
if (identity.website) printRow('Site web', identity.website);
|
|
73
|
+
|
|
74
|
+
// ── Preview mode stops here (one year of financials) ──────────────────────
|
|
75
|
+
if (isPreview) {
|
|
76
|
+
const lastFin = financialHistory[0];
|
|
77
|
+
section(' 💶 Derniers résultats financiers (preview)');
|
|
78
|
+
if (lastFin) {
|
|
79
|
+
printRow('Année', String(lastFin.annee));
|
|
80
|
+
printRow('Chiffre d\'affaires', lastFin.ca != null ? formatEuro(lastFin.ca) : null);
|
|
81
|
+
printRow('Résultat net', lastFin.resultat != null ? formatEuro(lastFin.resultat) : null);
|
|
82
|
+
printRow('Capitaux propres', lastFin.capitauxPropres != null ? formatEuro(lastFin.capitauxPropres) : null);
|
|
83
|
+
} else {
|
|
84
|
+
console.log(chalk.gray(' Données financières non disponibles.'));
|
|
85
|
+
}
|
|
86
|
+
console.log('');
|
|
87
|
+
console.log(chalk.yellow(` ⚡ Accédez au rapport complet avec Intelwatch Deep Profile : ${LICENSE_URL}`));
|
|
88
|
+
console.log('');
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
93
|
+
// FULL MODE (licensed users)
|
|
94
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
95
|
+
|
|
96
|
+
// ── Procédures collectives (alert at top if any) ──────────────────────────
|
|
97
|
+
if (proceduresCollectives.length > 0) {
|
|
98
|
+
section(' 🚨 Procédures collectives');
|
|
99
|
+
for (const p of proceduresCollectives) {
|
|
100
|
+
const label = [p.type, p.jugement].filter(Boolean).join(' — ');
|
|
101
|
+
const loc = p.tribunal ? ` (${p.tribunal})` : '';
|
|
102
|
+
console.log(chalk.red(` [${p.date || '?'}] ${label}${loc}`));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ── Dirigeants & mandats ───────────────────────────────────────────────────
|
|
107
|
+
if (dirigeants.length > 0) {
|
|
108
|
+
section(` 👔 Dirigeants (${dirigeants.length})`);
|
|
109
|
+
for (const d of dirigeants) {
|
|
110
|
+
const name = [d.prenom, d.nom].filter(Boolean).join(' ');
|
|
111
|
+
console.log('');
|
|
112
|
+
console.log(' ' + chalk.white.bold(name) + chalk.gray(` — ${d.role || '?'}`));
|
|
113
|
+
if (d.dateNomination) console.log(chalk.gray(` Nommé le : ${d.dateNomination}`));
|
|
114
|
+
if (d.nationalite) console.log(chalk.gray(` Nationalité : ${d.nationalite}`));
|
|
115
|
+
if (d.mandats.length > 0) {
|
|
116
|
+
console.log(chalk.gray(` Mandats (${d.mandats.length}) :`));
|
|
117
|
+
for (const m of d.mandats.slice(0, 6)) {
|
|
118
|
+
const dot = m.etat === 'actif' ? chalk.green('●') : chalk.gray('○');
|
|
119
|
+
const denom = m.denomination || m.siren || '?';
|
|
120
|
+
console.log(chalk.gray(` ${dot} ${denom} — ${m.role || '?'}`));
|
|
121
|
+
}
|
|
122
|
+
if (d.mandats.length > 6) {
|
|
123
|
+
console.log(chalk.gray(` ... et ${d.mandats.length - 6} autre(s)`));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
console.log('');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ── UBO ───────────────────────────────────────────────────────────────────
|
|
131
|
+
section(` 🔑 Bénéficiaires effectifs — UBO (${ubo.length})`);
|
|
132
|
+
if (ubo.length > 0) {
|
|
133
|
+
for (const b of ubo) {
|
|
134
|
+
const name = [b.prenom, b.nom].filter(Boolean).join(' ');
|
|
135
|
+
const stakes = [];
|
|
136
|
+
if (b.pourcentageParts != null) stakes.push(`${b.pourcentageParts}% parts`);
|
|
137
|
+
if (b.pourcentageVotes != null) stakes.push(`${b.pourcentageVotes}% votes`);
|
|
138
|
+
const stakeStr = stakes.length ? chalk.yellow(` — ${stakes.join(', ')}`) : '';
|
|
139
|
+
console.log(' ' + chalk.white(name) + stakeStr);
|
|
140
|
+
if (b.nationalite) console.log(chalk.gray(` Nationalité : ${b.nationalite}`));
|
|
141
|
+
if (b.dateNaissance) console.log(chalk.gray(` Né(e) le : ${b.dateNaissance}`));
|
|
142
|
+
}
|
|
143
|
+
} else {
|
|
144
|
+
console.log(chalk.gray(' Non disponible ou non déclaré.'));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ── Financial history table ────────────────────────────────────────────────
|
|
148
|
+
section(' 💶 Historique financier');
|
|
149
|
+
if (financialHistory.length > 0) {
|
|
150
|
+
const table = new Table({
|
|
151
|
+
head: ['Année', 'Chiffre d\'affaires', 'Résultat net', 'Capitaux propres'].map(h => chalk.cyan.bold(h)),
|
|
152
|
+
style: { head: [], border: ['grey'] },
|
|
153
|
+
colAligns: ['left', 'right', 'right', 'right'],
|
|
154
|
+
});
|
|
155
|
+
for (const f of financialHistory) {
|
|
156
|
+
table.push([
|
|
157
|
+
chalk.white(f.annee ?? '—'),
|
|
158
|
+
f.ca != null ? chalk.white(formatEuro(f.ca)) : chalk.gray('—'),
|
|
159
|
+
f.resultat != null
|
|
160
|
+
? (f.resultat >= 0 ? chalk.green(formatEuro(f.resultat)) : chalk.red(formatEuro(f.resultat)))
|
|
161
|
+
: chalk.gray('—'),
|
|
162
|
+
f.capitauxPropres != null
|
|
163
|
+
? (f.capitauxPropres >= 0 ? chalk.white(formatEuro(f.capitauxPropres)) : chalk.red(formatEuro(f.capitauxPropres)))
|
|
164
|
+
: chalk.gray('—'),
|
|
165
|
+
]);
|
|
166
|
+
}
|
|
167
|
+
console.log(table.toString());
|
|
168
|
+
} else {
|
|
169
|
+
console.log(chalk.gray(' Aucune donnée financière disponible.'));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ── BODACC publications ────────────────────────────────────────────────────
|
|
173
|
+
if (bodacc.length > 0) {
|
|
174
|
+
section(` 📰 Publications BODACC (${bodacc.length} dernières)`);
|
|
175
|
+
for (const pub of bodacc) {
|
|
176
|
+
const label = pub.description || pub.type || '?';
|
|
177
|
+
const trib = pub.tribunal ? chalk.gray(` — ${pub.tribunal}`) : '';
|
|
178
|
+
console.log(chalk.gray(` [${pub.date || '?'}] `) + chalk.white(label) + trib);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ── Digital footprint ─────────────────────────────────────────────────────
|
|
183
|
+
const websiteUrl = identity.website
|
|
184
|
+
? (identity.website.startsWith('http') ? identity.website : `https://${identity.website}`)
|
|
185
|
+
: null;
|
|
186
|
+
|
|
187
|
+
if (websiteUrl) {
|
|
188
|
+
section(' 🌐 Empreinte numérique');
|
|
189
|
+
console.log(chalk.gray(` Analyzing ${websiteUrl}...`));
|
|
190
|
+
try {
|
|
191
|
+
const siteData = await analyzeSite(websiteUrl);
|
|
192
|
+
if (siteData.error) {
|
|
193
|
+
warn(` Site non accessible: ${siteData.error}`);
|
|
194
|
+
} else {
|
|
195
|
+
const techNames = (siteData.techStack || []).map(t => t.name).join(', ') || 'aucune détectée';
|
|
196
|
+
printRow('Technologies', techNames);
|
|
197
|
+
if (siteData.performance) {
|
|
198
|
+
printRow('Performance', `${siteData.performance.responseTimeMs}ms, ${siteData.performance.htmlSizeKB} KB`);
|
|
199
|
+
}
|
|
200
|
+
if (siteData.security) {
|
|
201
|
+
const s = siteData.security;
|
|
202
|
+
const score = [s.https, s.hsts, s.xFrameOptions, s.csp, s.xContentType].filter(Boolean).length;
|
|
203
|
+
printRow('Sécurité', `${score}/5 (HTTPS:${s.https ? '✓' : '✗'} HSTS:${s.hsts ? '✓' : '✗'} CSP:${s.csp ? '✓' : '✗'})`);
|
|
204
|
+
}
|
|
205
|
+
if (siteData.socialLinks && Object.keys(siteData.socialLinks).length > 0) {
|
|
206
|
+
printRow('Réseaux sociaux', Object.keys(siteData.socialLinks).join(', '));
|
|
207
|
+
}
|
|
208
|
+
if (siteData.contentStats?.recentArticles?.length > 0) {
|
|
209
|
+
printRow('Blog', `${siteData.contentStats.articleCount} articles récents`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
} catch (e) {
|
|
213
|
+
warn(` Impossible d'analyser le site: ${e.message}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ── Press & mentions ───────────────────────────────────────────────────────
|
|
218
|
+
if (identity.name) {
|
|
219
|
+
section(' 📣 Presse & réputation');
|
|
220
|
+
console.log(chalk.gray(` Searching mentions for "${identity.name}"...`));
|
|
221
|
+
try {
|
|
222
|
+
const press = await searchPressMentions(identity.name);
|
|
223
|
+
if (press.mentionCount > 0) {
|
|
224
|
+
const bd = press.mentions.reduce((acc, m) => {
|
|
225
|
+
const k = /positive/.test(m.sentiment) ? 'positive'
|
|
226
|
+
: /negative/.test(m.sentiment) ? 'negative' : 'neutral';
|
|
227
|
+
acc[k] = (acc[k] || 0) + 1;
|
|
228
|
+
return acc;
|
|
229
|
+
}, {});
|
|
230
|
+
console.log(chalk.magenta(` ${press.mentionCount} mentions | 👍${bd.positive || 0} 😐${bd.neutral || 0} 👎${bd.negative || 0}`));
|
|
231
|
+
for (const m of press.mentions.slice(0, 8)) {
|
|
232
|
+
const emoji = /positive/.test(m.sentiment) ? '👍' : /negative/.test(m.sentiment) ? '👎' : '😐';
|
|
233
|
+
console.log(chalk.gray(` ${emoji} [${m.category}] ${(m.title || '').substring(0, 80)} (${m.domain})`));
|
|
234
|
+
}
|
|
235
|
+
} else {
|
|
236
|
+
console.log(chalk.gray(' Aucune mention récente trouvée.'));
|
|
237
|
+
}
|
|
238
|
+
} catch (e) {
|
|
239
|
+
warn(` Press search failed: ${e.message}`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ── AI Summary ────────────────────────────────────────────────────────────
|
|
244
|
+
if (options.ai) {
|
|
245
|
+
section(' 🤖 Synthèse IA — Due Diligence');
|
|
246
|
+
if (!hasAIKey()) {
|
|
247
|
+
warn(' No AI API key. Set OPENAI_API_KEY or ANTHROPIC_API_KEY.');
|
|
248
|
+
} else {
|
|
249
|
+
console.log(chalk.gray(' Generating AI due diligence summary...'));
|
|
250
|
+
try {
|
|
251
|
+
const finSummary = financialHistory
|
|
252
|
+
.map(f => `${f.annee}: CA=${f.ca != null ? formatEuro(f.ca) : 'N/A'}, Résultat=${f.resultat != null ? formatEuro(f.resultat) : 'N/A'}, CP=${f.capitauxPropres != null ? formatEuro(f.capitauxPropres) : 'N/A'}`)
|
|
253
|
+
.join('\n') || 'Non disponible';
|
|
254
|
+
|
|
255
|
+
const dirStr = dirigeants
|
|
256
|
+
.map(d => `${d.prenom || ''} ${d.nom || ''} (${d.role || '?'}): ${d.mandats.length} mandats`)
|
|
257
|
+
.join(', ') || 'Non disponible';
|
|
258
|
+
|
|
259
|
+
const uboStr = ubo
|
|
260
|
+
.map(b => `${b.prenom || ''} ${b.nom || ''}: ${b.pourcentageParts ?? '?'}% parts`)
|
|
261
|
+
.join(', ') || 'Non déclaré';
|
|
262
|
+
|
|
263
|
+
const procStr = proceduresCollectives.length
|
|
264
|
+
? proceduresCollectives.map(p => `${p.date || '?'}: ${p.type || '?'}`).join(', ')
|
|
265
|
+
: 'Aucune';
|
|
266
|
+
|
|
267
|
+
const systemPrompt = 'Tu es un analyste Deep Profile expert. Rédige une synthèse de due diligence concise et professionnelle en français.';
|
|
268
|
+
const userPrompt = `Synthèse due diligence pour ${identity.name} (SIREN: ${identity.siren})
|
|
269
|
+
|
|
270
|
+
**Identité**
|
|
271
|
+
- Forme: ${identity.formeJuridique || '?'}
|
|
272
|
+
- Création: ${identity.dateCreation || '?'}
|
|
273
|
+
- Effectifs: ${identity.effectifs || '?'}
|
|
274
|
+
- NAF: ${identity.nafCode} — ${identity.nafLabel}
|
|
275
|
+
- Capital: ${identity.capital != null ? formatEuro(identity.capital) : '?'}
|
|
276
|
+
|
|
277
|
+
**Dirigeants**
|
|
278
|
+
${dirStr}
|
|
279
|
+
|
|
280
|
+
**UBO (${ubo.length})**
|
|
281
|
+
${uboStr}
|
|
282
|
+
|
|
283
|
+
**Historique financier**
|
|
284
|
+
${finSummary}
|
|
285
|
+
|
|
286
|
+
**Procédures collectives**
|
|
287
|
+
${procStr}
|
|
288
|
+
|
|
289
|
+
**Publications BODACC récentes**
|
|
290
|
+
${bodacc.slice(0, 5).map(b => `${b.date || '?'}: ${b.type || '?'}`).join(', ') || 'Aucune'}
|
|
291
|
+
|
|
292
|
+
Rédige une synthèse en 5 points : 1) Profil, 2) Gouvernance & actionnariat, 3) Situation financière, 4) Risques identifiés, 5) Points d'attention pour l'acquéreur. Sois factuel, max 400 mots.`;
|
|
293
|
+
|
|
294
|
+
const summary = await callAI(systemPrompt, userPrompt, { maxTokens: 600 });
|
|
295
|
+
console.log('\n' + chalk.white(summary) + '\n');
|
|
296
|
+
} catch (e) {
|
|
297
|
+
warn(` AI summary failed: ${e.message}`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// ── Footer ─────────────────────────────────────────────────────────────────
|
|
303
|
+
console.log('');
|
|
304
|
+
const today = new Date().toLocaleDateString('fr-FR', { year: 'numeric', month: 'long', day: 'numeric' });
|
|
305
|
+
console.log(chalk.gray(` Source : Pappers.fr — ${today}`));
|
|
306
|
+
console.log('');
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
310
|
+
|
|
311
|
+
function printRow(label, value, coloredValue) {
|
|
312
|
+
const padded = label.padEnd(16);
|
|
313
|
+
const display = coloredValue ?? (value != null ? chalk.white(value) : chalk.gray('—'));
|
|
314
|
+
console.log(chalk.gray(` ${padded}: `) + display);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function formatNum(n) {
|
|
318
|
+
return Number(n).toLocaleString('fr-FR');
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function formatEuro(n) {
|
|
322
|
+
if (n == null) return '—';
|
|
323
|
+
const abs = Math.abs(n);
|
|
324
|
+
const sign = n < 0 ? '-' : '';
|
|
325
|
+
if (abs >= 1_000_000_000) return `${sign}${(abs / 1_000_000_000).toFixed(2).replace('.', ',')} Md€`;
|
|
326
|
+
if (abs >= 1_000_000) return `${sign}${(abs / 1_000_000).toFixed(1).replace('.', ',')} M€`;
|
|
327
|
+
if (abs >= 1_000) return `${sign}${formatNum(Math.round(abs / 1_000))} K€`;
|
|
328
|
+
return `${sign}${formatNum(abs)} €`;
|
|
329
|
+
}
|
package/src/index.js
CHANGED
|
@@ -11,6 +11,7 @@ import { setupNotifications } from './commands/notify.js';
|
|
|
11
11
|
import { listTrackers, removeTrackerCmd } from './commands/list.js';
|
|
12
12
|
import { runAISummary } from './commands/ai-summary.js';
|
|
13
13
|
import { runPitch } from './commands/pitch.js';
|
|
14
|
+
import { runMA } from './commands/profile.js';
|
|
14
15
|
|
|
15
16
|
const program = new Command();
|
|
16
17
|
|
|
@@ -154,6 +155,17 @@ program
|
|
|
154
155
|
await runPitch(trackerId, options);
|
|
155
156
|
});
|
|
156
157
|
|
|
158
|
+
// ─── m-a ──────────────────────────────────────────────────────────────────────
|
|
159
|
+
|
|
160
|
+
program
|
|
161
|
+
.command('profile <siren-or-name>')
|
|
162
|
+
.description('M&A due diligence report for a French company (requires Pro license)')
|
|
163
|
+
.option('--preview', 'Run limited preview: company identity + last year financials only')
|
|
164
|
+
.option('--ai', 'Generate an AI-powered due diligence summary (requires AI API key)')
|
|
165
|
+
.action(async (sirenOrName, options) => {
|
|
166
|
+
await runMA(sirenOrName, options);
|
|
167
|
+
});
|
|
168
|
+
|
|
157
169
|
// ─── discover ─────────────────────────────────────────────────────────────────
|
|
158
170
|
|
|
159
171
|
program
|
package/src/scrapers/pappers.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import axios from 'axios';
|
|
2
2
|
|
|
3
3
|
const PAPPERS_API = 'https://api.pappers.fr/v1';
|
|
4
|
+
const PAPPERS_API_V2 = 'https://api.pappers.fr/v2';
|
|
4
5
|
|
|
5
6
|
function getApiKey() {
|
|
6
7
|
return process.env.PAPPERS_API_KEY || null;
|
|
@@ -71,6 +72,104 @@ export async function pappersLookup(companyName) {
|
|
|
71
72
|
return formatPappersDetail(detail.data);
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Get full M&A dossier for a company by SIREN.
|
|
77
|
+
* Returns parsed financial history, UBO, BODACC, dirigeants with mandats,
|
|
78
|
+
* and collective procedures.
|
|
79
|
+
*/
|
|
80
|
+
export async function pappersGetFullDossier(siren) {
|
|
81
|
+
const apiKey = getApiKey();
|
|
82
|
+
if (!apiKey) return { data: null, error: 'No PAPPERS_API_KEY set' };
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
const resp = await axios.get(`${PAPPERS_API}/entreprise`, {
|
|
86
|
+
params: { api_token: apiKey, siren },
|
|
87
|
+
timeout: 15000,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const d = resp.data;
|
|
91
|
+
|
|
92
|
+
// Financial history — last 5 years
|
|
93
|
+
const financialHistory = (d.finances || []).slice(0, 5).map(f => ({
|
|
94
|
+
annee: f.annee,
|
|
95
|
+
ca: f.chiffre_affaires ?? null,
|
|
96
|
+
resultat: f.resultat ?? null,
|
|
97
|
+
capitauxPropres: f.capitaux_propres ?? null,
|
|
98
|
+
effectif: f.effectif ?? null,
|
|
99
|
+
}));
|
|
100
|
+
|
|
101
|
+
// UBO — bénéficiaires effectifs
|
|
102
|
+
const ubo = (d.beneficiaires_effectifs || []).map(b => ({
|
|
103
|
+
nom: b.nom,
|
|
104
|
+
prenom: b.prenom,
|
|
105
|
+
dateNaissance: b.date_de_naissance_formate || b.date_naissance || null,
|
|
106
|
+
nationalite: b.nationalite || null,
|
|
107
|
+
pourcentageParts: b.pourcentage_parts ?? null,
|
|
108
|
+
pourcentageVotes: b.pourcentage_votes ?? null,
|
|
109
|
+
}));
|
|
110
|
+
|
|
111
|
+
// BODACC publications — last 10
|
|
112
|
+
const bodacc = (d.publications_bodacc || []).slice(0, 10).map(p => ({
|
|
113
|
+
date: p.date,
|
|
114
|
+
type: p.type,
|
|
115
|
+
tribunal: p.tribunal || null,
|
|
116
|
+
numero: p.numero_annonce || null,
|
|
117
|
+
description: p.acte?.actes_publies?.[0]?.type_acte || p.type || null,
|
|
118
|
+
}));
|
|
119
|
+
|
|
120
|
+
// Dirigeants with their mandats in other companies
|
|
121
|
+
const dirigeants = (d.dirigeants || []).map(dir => ({
|
|
122
|
+
nom: dir.nom,
|
|
123
|
+
prenom: dir.prenom,
|
|
124
|
+
role: dir.fonction,
|
|
125
|
+
dateNomination: dir.date_prise_de_poste || null,
|
|
126
|
+
dateNaissance: dir.date_de_naissance_formate || null,
|
|
127
|
+
nationalite: dir.nationalite || null,
|
|
128
|
+
mandats: (dir.entreprises_dirigees || []).map(e => ({
|
|
129
|
+
siren: e.siren,
|
|
130
|
+
denomination: e.denomination || e.nom_entreprise || null,
|
|
131
|
+
role: e.fonction || null,
|
|
132
|
+
etat: e.etat || null,
|
|
133
|
+
})),
|
|
134
|
+
}));
|
|
135
|
+
|
|
136
|
+
// Procédures collectives
|
|
137
|
+
const proceduresCollectives = (d.procedures_collectives || []).map(p => ({
|
|
138
|
+
date: p.date_effet || p.date || null,
|
|
139
|
+
type: p.type || null,
|
|
140
|
+
jugement: p.nature_jugement || null,
|
|
141
|
+
tribunal: p.tribunal || null,
|
|
142
|
+
}));
|
|
143
|
+
|
|
144
|
+
// Company identity
|
|
145
|
+
const identity = {
|
|
146
|
+
siren: d.siren,
|
|
147
|
+
siret: d.siege?.siret || null,
|
|
148
|
+
name: d.nom_entreprise || d.denomination || null,
|
|
149
|
+
dateCreation: d.date_creation || null,
|
|
150
|
+
nafCode: d.code_naf || null,
|
|
151
|
+
nafLabel: d.libelle_code_naf || null,
|
|
152
|
+
formeJuridique: d.forme_juridique || null,
|
|
153
|
+
effectifs: d.tranche_effectif || d.effectif || null,
|
|
154
|
+
adresse: d.siege?.adresse_ligne_1 || d.siege?.adresse || null,
|
|
155
|
+
ville: d.siege?.ville || null,
|
|
156
|
+
codePostal: d.siege?.code_postal || null,
|
|
157
|
+
capital: d.capital ?? null,
|
|
158
|
+
capitalMonnaie: d.devise_capital || 'EUR',
|
|
159
|
+
website: d.site_internet || d.domaine_de_messagerie || null,
|
|
160
|
+
status: d.etat === 'actif' ? 'Actif' : (d.etat || 'Inconnu'),
|
|
161
|
+
dateRadiation: d.date_radiation || null,
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
data: { identity, financialHistory, ubo, bodacc, dirigeants, proceduresCollectives, raw: d },
|
|
166
|
+
error: null,
|
|
167
|
+
};
|
|
168
|
+
} catch (err) {
|
|
169
|
+
return { data: null, error: err.message };
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
74
173
|
function formatPappersResult(r) {
|
|
75
174
|
return {
|
|
76
175
|
siren: r.siren,
|