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
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# CHANGELOG-DRAFT — intelwatch v1.3.0
|
|
2
|
+
|
|
3
|
+
## ✨ New Features
|
|
4
|
+
|
|
5
|
+
### International Provider Architecture (`src/providers/`)
|
|
6
|
+
- **Provider Registry** (`registry.js`) — adapts company data source based on domain TLD
|
|
7
|
+
- `.fr` → Pappers (full French company data)
|
|
8
|
+
- `.com`, `.de`, `.uk`, etc. → OpenCorporates (180+ jurisdictions)
|
|
9
|
+
- Clearbit available as BYOK enrichment layer (domain → sector, size, revenue, tech)
|
|
10
|
+
- **Auto-detection** : `detectCountry()` maps TLD to ISO country code, `resolveProvider()` picks the best provider
|
|
11
|
+
- **Unified API** : `searchCompany()`, `getCompanyProfile()`, `getSubsidiaries()`, `lookupCompany()` — same interface, any provider
|
|
12
|
+
- **License-integrated** : full profile = Pro only, subsidiaries = Pro only, preview mode for Free tier
|
|
13
|
+
- **Pappers provider** (`pappers.js`) — thin adapter wrapping existing `scrapers/pappers.js`
|
|
14
|
+
- **OpenCorporates provider** (`opencorporates.js`) — real API integration with free tier (500 req/month, no key needed) + BYOK for higher limits
|
|
15
|
+
- **Clearbit provider** (`clearbit.js`) — scaffold with real API integration (BYOK: `CLEARBIT_API_KEY`)
|
|
16
|
+
- Competitor tracker refactored: uses `lookupCompany()` instead of direct Pappers import → works for any TLD
|
|
17
|
+
- Backward compat: `pappers` key still present in competitor snapshots for existing data
|
|
18
|
+
|
|
19
|
+
### Freemium License Gate
|
|
20
|
+
- **Centralized `src/license.js`** — single module for all Pro/Free tier logic
|
|
21
|
+
- License detection: `INTELWATCH_PRO_KEY` env → `INTELWATCH_LICENSE_KEY` env → `~/.intelwatch-license` file
|
|
22
|
+
- **Free tier:** JSON + CSV (50 rows), Reddit/HN (5 results), Pappers preview only
|
|
23
|
+
- **Pro tier:** JSON + CSV + XLS + PDF (unlimited), Reddit/HN (100), full profiles + subsidiaries
|
|
24
|
+
|
|
25
|
+
### Export System (CSV, XLS, PDF)
|
|
26
|
+
- `handleExport()` with license-aware gating — XLS/PDF throw `LICENSE_REQUIRED`
|
|
27
|
+
- CSV capped at 50 rows on Free tier with warning
|
|
28
|
+
|
|
29
|
+
### Reddit & Hacker News Mentions
|
|
30
|
+
- `src/scrapers/reddit-hn.js` — Reddit JSON API + HN Algolia API
|
|
31
|
+
- Results capped per license tier (Free: 5, Pro: 100)
|
|
32
|
+
- Brand tracker fetches Google News + Reddit + HN in parallel
|
|
33
|
+
|
|
34
|
+
## 🐛 Bug Fixes
|
|
35
|
+
- `handleError()` crash on null/undefined — graceful handling added
|
|
36
|
+
- Provider registry returns `tier`/`isPreview` even when provider unavailable
|
|
37
|
+
|
|
38
|
+
## 🧪 Tests — 136 pass, 0 fail (was 40)
|
|
39
|
+
- `test/providers.test.js` — 31 tests: detectCountry (11 TLDs), resolveProvider (5), listProviders (3), interface compliance (3), searchCompany (3), getCompanyProfile (2), getSubsidiaries license gate (1), individual provider availability (3)
|
|
40
|
+
- `test/license.test.js` — 19 tests
|
|
41
|
+
- `test/export.test.js` — 33 tests (incl. LICENSE_REQUIRED gates)
|
|
42
|
+
- `test/i18n.test.js` — 6 tests
|
|
43
|
+
- `test/error-handler.test.js` — 5 tests
|
|
44
|
+
- `test/reddit-hn.test.js` — 4 tests
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
## [1.3.2] - 2026-03-21
|
|
2
|
+
### Added
|
|
3
|
+
- **Google Gemini Provider**: Full support for Gemini models via Google API (`GEMINI_API_KEY` or `GOOGLE_API_KEY`) for Due Diligence AI analysis
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- **BODACC Limit**: Reduced BODACC publication injection from 50 to 5 entries for AI context to drastically reduce token usage and prevent truncation
|
|
7
|
+
- **M&A Output**: Reduced AI verbosity on health scores by enforcing strict bullet points constraints
|
|
8
|
+
- **MaxTokens Override**: Removed hardcoded `maxTokens=1000` default and bumped to `8192` to allow full M&A history to generate without arbitrary cutoff
|
|
9
|
+
- **Group Structure Render**: Fixed logic that accidentally injected Private Equity shareholders (like BPIFrance) into the subsidiaries array
|
|
10
|
+
|
|
1
11
|
# CHANGELOG - v1.2 Draft
|
|
2
12
|
|
|
3
13
|
## Version 1.2.0 (en développement)
|
|
@@ -167,3 +177,23 @@ All notable changes to this project will be documented in this file.
|
|
|
167
177
|
### Fixed
|
|
168
178
|
- Array headers bug in tech-detect.js
|
|
169
179
|
- Anthropic model name updated to `claude-3-5-haiku-latest`
|
|
180
|
+
|
|
181
|
+
## [1.3.0] - 2026-03-21
|
|
182
|
+
### Added
|
|
183
|
+
- Pro License Paywall: Gated advanced features (PDF/XLS export, Deep Profile, International OSINT).
|
|
184
|
+
- Stripe Payment Link integration for Intelwatch Pro subscriptions.
|
|
185
|
+
- International Smart Routing: `.fr` hits Pappers, international hits Apollo/Clearbit/OpenCorporates.
|
|
186
|
+
- France Handoff: International companies based in France are handed off to Pappers for deeper financial data.
|
|
187
|
+
- Reddit JSON API & HackerNews Algolia integration for digital OSINT and sentiment tracking.
|
|
188
|
+
- M&A Deep Dorks: Restricts Brave Search queries to specialized PE/M&A news sources (cfnews, lesechos, fusacq, etc.)
|
|
189
|
+
- Export capabilities unified under `--export <json|csv|xls|pdf>` flag.
|
|
190
|
+
|
|
191
|
+
### Fixed
|
|
192
|
+
- M&A History PDF generation bug: Stopped truncating AI timeline events, full timeline is now preserved.
|
|
193
|
+
- Group Structure Classification: Prevented PE Funds (BPIFrance, IK Partners, etc.) from being improperly categorized as operational subsidiaries in the AI due diligence report.
|
|
194
|
+
- Fixed `pdfData` passthrough bug that caused empty PDF exports.
|
|
195
|
+
|
|
196
|
+
### v1.3.1 (2026-03-21)
|
|
197
|
+
- **Bug Fix**: Fixed an issue where PDF exports could contain empty Group Structure and M&A History sections leading to ugly page breaks.
|
|
198
|
+
- **Bug Fix**: Preserved AI-generated subsidiaries when registry fallback has missing revenue data.
|
|
199
|
+
- **Bug Fix**: Fixed PDF context scope throwing an undefined error when exporting via the `--export pdf` flag instead of the legacy `--format` option.
|
|
Binary file
|
package/RELEASE.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
✅ `intelwatch@1.2.0` est publié sur npm !
|
|
2
|
+
|
|
3
|
+
```bash
|
|
4
|
+
npm install -g intelwatch
|
|
5
|
+
```
|
|
6
|
+
|
|
7
|
+
### Ce qui est en ligne :
|
|
8
|
+
- Les bugs de parsing AI corrigés en prod
|
|
9
|
+
- L'export JSON et CSV natif pour les rapports
|
|
10
|
+
- Les `--lang fr` qui adaptent les rapports AI
|
|
11
|
+
- La détection de 56 technos (Nuxt, Vercel, Tailwind, etc.)
|
|
12
|
+
|
|
13
|
+
**Note Git** : J'ai fait le commit local de la v1.2.0, mais GitHub a refusé mon push (`403 Permission to Recognity/intelwatch.git denied to ashroth1`). Tu pourras faire un `git push origin main --tags` depuis ta machine quand tu as le temps.
|
|
14
|
+
|
|
15
|
+
Dis-moi si tu veux que le sous-agent attaque un autre CLI ou une autre feature d'intelwatch (ex: Inpi, ou détection SaaS) cette nuit !
|
package/export.pdf
ADDED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "intelwatch",
|
|
3
|
-
"version": "1.2
|
|
3
|
+
"version": "1.3.2",
|
|
4
4
|
"description": "Competitive intelligence CLI — track competitors, keywords, and brand mentions from the terminal",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"intelwatch": "./bin/intelwatch.js"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
|
-
"test": "node --test test
|
|
11
|
+
"test": "node --test test/storage.test.js test/sentiment.test.js test/tech-detect.test.js test/export.test.js test/i18n.test.js test/error-handler.test.js test/reddit-hn.test.js test/license.test.js test/providers.test.js",
|
|
12
12
|
"start": "node bin/intelwatch.js"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"cli-table3": "^0.6.5",
|
|
20
20
|
"commander": "^12.1.0",
|
|
21
21
|
"inquirer": "^10.2.2",
|
|
22
|
+
"xlsx": "^0.18.5",
|
|
22
23
|
"yaml": "^2.6.1"
|
|
23
24
|
},
|
|
24
25
|
"engines": {
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/src/ai/client.js
CHANGED
|
@@ -5,9 +5,13 @@ import { loadConfig } from '../config.js';
|
|
|
5
5
|
* Returns null if no key is configured.
|
|
6
6
|
*/
|
|
7
7
|
export function getAIConfig() {
|
|
8
|
+
const envGoogle = process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY;
|
|
8
9
|
const envOpenAI = process.env.OPENAI_API_KEY;
|
|
9
10
|
const envAnthropic = process.env.ANTHROPIC_API_KEY;
|
|
10
11
|
|
|
12
|
+
if (envGoogle) {
|
|
13
|
+
return { provider: 'google', apiKey: envGoogle, model: 'gemini-2.5-flash' };
|
|
14
|
+
}
|
|
11
15
|
if (envOpenAI) {
|
|
12
16
|
return { provider: 'openai', apiKey: envOpenAI, model: 'gpt-4o-mini' };
|
|
13
17
|
}
|
|
@@ -47,7 +51,7 @@ export async function callAI(systemPrompt, userPrompt, options = {}) {
|
|
|
47
51
|
const aiConfig = getAIConfig();
|
|
48
52
|
if (!aiConfig) {
|
|
49
53
|
throw new Error(
|
|
50
|
-
'No AI API key configured. Set OPENAI_API_KEY or ANTHROPIC_API_KEY, ' +
|
|
54
|
+
'No AI API key configured. Set GEMINI_API_KEY, OPENAI_API_KEY or ANTHROPIC_API_KEY, ' +
|
|
51
55
|
'or add ai.api_key to ~/.intelwatch/config.yml'
|
|
52
56
|
);
|
|
53
57
|
}
|
|
@@ -55,6 +59,9 @@ export async function callAI(systemPrompt, userPrompt, options = {}) {
|
|
|
55
59
|
const { provider, apiKey, model } = aiConfig;
|
|
56
60
|
const maxTokens = options.maxTokens || 1000;
|
|
57
61
|
|
|
62
|
+
if (provider === 'google') {
|
|
63
|
+
return callGoogle(apiKey, model, systemPrompt, userPrompt, maxTokens);
|
|
64
|
+
}
|
|
58
65
|
if (provider === 'anthropic') {
|
|
59
66
|
return callAnthropic(apiKey, model, systemPrompt, userPrompt, maxTokens);
|
|
60
67
|
}
|
|
@@ -128,3 +135,34 @@ export function estimateCost(inputChars, outputChars, provider = 'openai') {
|
|
|
128
135
|
const cost = inputTokens * rates.in + outputTokens * rates.out;
|
|
129
136
|
return { inputTokens, outputTokens, cost: cost.toFixed(5) };
|
|
130
137
|
}
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
async function callGoogle(apiKey, model, systemPrompt, userPrompt, maxTokens) {
|
|
141
|
+
// Use v1beta for Gemini 2.5
|
|
142
|
+
const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${apiKey}`;
|
|
143
|
+
const res = await fetch(url, {
|
|
144
|
+
method: 'POST',
|
|
145
|
+
headers: {
|
|
146
|
+
'Content-Type': 'application/json',
|
|
147
|
+
},
|
|
148
|
+
body: JSON.stringify({
|
|
149
|
+
systemInstruction: { parts: [{ text: systemPrompt }] },
|
|
150
|
+
contents: [{ role: 'user', parts: [{ text: userPrompt }] }],
|
|
151
|
+
generationConfig: {
|
|
152
|
+
maxOutputTokens: maxTokens,
|
|
153
|
+
temperature: 0.2
|
|
154
|
+
}
|
|
155
|
+
}),
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
if (!res.ok) {
|
|
159
|
+
const body = await res.text();
|
|
160
|
+
throw new Error(`Google API ${res.status}: ${body.slice(0, 200)}`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const data = await res.json();
|
|
164
|
+
if (!data.candidates || !data.candidates[0].content) {
|
|
165
|
+
throw new Error('Invalid Google API response');
|
|
166
|
+
}
|
|
167
|
+
return data.candidates[0].content.parts[0].text.trim();
|
|
168
|
+
}
|
package/src/commands/profile.js
CHANGED
|
@@ -6,13 +6,12 @@ import { analyzeSite } from '../scrapers/site-analyzer.js';
|
|
|
6
6
|
import { callAI, hasAIKey } from '../ai/client.js';
|
|
7
7
|
import { header, section, warn, error } from '../utils/display.js';
|
|
8
8
|
import { generatePDF } from '@recognity/pdf-report';
|
|
9
|
-
import {
|
|
9
|
+
import { handleExport, formatForExport } from '../utils/export.js';
|
|
10
10
|
import { setLanguage, getLanguage, t, getPrompt } from '../utils/i18n.js';
|
|
11
|
-
|
|
12
|
-
const LICENSE_URL = 'https://recognity.fr/tools/intelwatch';
|
|
11
|
+
import { isPro, printProUpgrade } from '../license.js';
|
|
13
12
|
|
|
14
13
|
export async function runMA(sirenOrName, options) {
|
|
15
|
-
const hasLicense =
|
|
14
|
+
const hasLicense = isPro();
|
|
16
15
|
const isPreview = !!options.preview;
|
|
17
16
|
|
|
18
17
|
// Set language from global option (passed from main program)
|
|
@@ -22,17 +21,14 @@ export async function runMA(sirenOrName, options) {
|
|
|
22
21
|
|
|
23
22
|
// ── License gate ───────────────────────────────────────────────────────────
|
|
24
23
|
if (!hasLicense && !isPreview) {
|
|
25
|
-
|
|
26
|
-
console.log(chalk.
|
|
27
|
-
console.log(chalk.gray(` Get yours at ${LICENSE_URL}\n`));
|
|
28
|
-
console.log(chalk.gray(' Run with --preview for a limited preview (company identity + last year financials only).'));
|
|
29
|
-
console.log('');
|
|
24
|
+
printProUpgrade('Deep Profile Due Diligence');
|
|
25
|
+
console.log(chalk.gray(' Run with --preview for a limited preview (company identity + last year financials only).\n'));
|
|
30
26
|
process.exit(1);
|
|
31
27
|
}
|
|
32
28
|
|
|
33
29
|
if (isPreview && !hasLicense) {
|
|
34
30
|
console.log(chalk.yellow(' ⚡ PREVIEW MODE — Company identity + last year financials only'));
|
|
35
|
-
|
|
31
|
+
printProUpgrade('Full company profile');
|
|
36
32
|
}
|
|
37
33
|
|
|
38
34
|
// ── SIREN or name lookup ───────────────────────────────────────────────────
|
|
@@ -310,11 +306,12 @@ export async function runMA(sirenOrName, options) {
|
|
|
310
306
|
const press = await searchPressMentions(brandName);
|
|
311
307
|
pressResults = press.mentions || [];
|
|
312
308
|
|
|
313
|
-
// Additional M&A-focused search to catch acquisitions/deals
|
|
309
|
+
// Additional M&A-focused search to catch acquisitions/deals (dorks: quality M&A sources only)
|
|
310
|
+
const MA_SITE_DORKS = '(site:fusacq.com OR site:cfnews.net OR site:lesechos.fr OR site:maddyness.com OR site:agefi.fr)';
|
|
314
311
|
try {
|
|
315
312
|
const { braveWebSearch } = await import('../scrapers/brave-search.js');
|
|
316
313
|
await new Promise(r => setTimeout(r, 600));
|
|
317
|
-
const maSearch = await braveWebSearch(`"${brandName}" acquisition OR rachat OR "entrée au capital" OR "prise de participation"`, { count: 10 });
|
|
314
|
+
const maSearch = await braveWebSearch(`"${brandName}" (acquisition OR LBO OR rachat OR "levée de fonds" OR "entrée au capital" OR "prise de participation") ${MA_SITE_DORKS}`, { count: 10 });
|
|
318
315
|
for (const r of (maSearch.results || [])) {
|
|
319
316
|
const text = ((r.title || '') + ' ' + (r.snippet || '')).toLowerCase();
|
|
320
317
|
if (!text.includes(brandName.toLowerCase())) continue;
|
|
@@ -802,6 +799,7 @@ OBLIGATOIRE :
|
|
|
802
799
|
- ${getPrompt('strengthsWeaknessesRules')}
|
|
803
800
|
- Minimum 5 concurrents de taille comparable (CA consolidé similaire, même code NAF ${identity.nafCode || ''})
|
|
804
801
|
- Le score de santé doit être basé sur les finances CONSOLIDÉES si disponibles
|
|
802
|
+
- BE EXTREMELY CONCISE. Use bullet points and short sentences. Max 30 words per field.
|
|
805
803
|
- Ne mentionne JAMAIS que la holding a peu d'employés comme faiblesse — c'est normal pour une holding, les employés sont dans les filiales
|
|
806
804
|
- maHistory: The PRE-BUILT M&A TIMELINE above contains ALL entries with AUTHORITATIVE dates and types.
|
|
807
805
|
RULES:
|
|
@@ -820,23 +818,33 @@ OBLIGATOIRE :
|
|
|
820
818
|
- aiComment: 3-4 sentences. Compare deposited (62M€ 2024) vs announced/projected. Be specific. If multiple revenue targets exist (e.g. 100M€ and 300M€), explain both.
|
|
821
819
|
- aiComment: 3-4 sentences comparing deposited vs announced/projected, discussing growth sustainability and outlook`;
|
|
822
820
|
|
|
823
|
-
const raw = await callAI(systemPrompt, userPrompt, { maxTokens:
|
|
821
|
+
const raw = await callAI(systemPrompt, userPrompt, { maxTokens: 8192 });
|
|
824
822
|
aiAnalysis = extractAIJSON(raw);
|
|
825
823
|
|
|
826
|
-
//
|
|
827
|
-
if (aiAnalysis
|
|
824
|
+
// M&A History: Merging code-built events with AI events instead of overwriting
|
|
825
|
+
if (aiAnalysis) {
|
|
828
826
|
const aiMa = aiAnalysis.maHistory || [];
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
827
|
+
|
|
828
|
+
// Add AI identified M&A events that aren't in codeBuiltMaHistory
|
|
829
|
+
const mergedMaHistory = [...codeBuiltMaHistory];
|
|
830
|
+
|
|
831
|
+
for (const aiEntry of aiMa) {
|
|
832
|
+
const targetKey = (aiEntry.target || '').toLowerCase().split(' ')[0];
|
|
833
|
+
const exists = mergedMaHistory.some(c => (c.target || '').toLowerCase().includes(targetKey));
|
|
834
|
+
|
|
835
|
+
if (!exists && targetKey.length > 2) {
|
|
836
|
+
mergedMaHistory.push({
|
|
837
|
+
date: aiEntry.date || aiEntry.year || 'Unknown',
|
|
838
|
+
target: aiEntry.target,
|
|
839
|
+
type: aiEntry.type || 'Acquisition',
|
|
840
|
+
description: aiEntry.description || aiEntry.rationale || ''
|
|
841
|
+
});
|
|
837
842
|
}
|
|
838
843
|
}
|
|
839
|
-
|
|
844
|
+
|
|
845
|
+
// Sort by date (descending string comparison is mostly ok for YYYY-MM)
|
|
846
|
+
mergedMaHistory.sort((a, b) => b.date.localeCompare(a.date));
|
|
847
|
+
aiAnalysis.maHistory = mergedMaHistory;
|
|
840
848
|
}
|
|
841
849
|
|
|
842
850
|
if (aiAnalysis) {
|
|
@@ -937,8 +945,9 @@ OBLIGATOIRE :
|
|
|
937
945
|
}
|
|
938
946
|
}
|
|
939
947
|
|
|
948
|
+
let pdfData = null;
|
|
940
949
|
// ── PDF export ──────────────────────────────────────────────────────────────
|
|
941
|
-
if (options.format === 'pdf') {
|
|
950
|
+
if (options.format === 'pdf' || options.export === 'pdf') {
|
|
942
951
|
const outputPath = options.output || `profile-${siren}.pdf`;
|
|
943
952
|
const fmtEuro = (n) => {
|
|
944
953
|
if (n == null) return '—';
|
|
@@ -957,22 +966,24 @@ OBLIGATOIRE :
|
|
|
957
966
|
});
|
|
958
967
|
}
|
|
959
968
|
|
|
960
|
-
|
|
969
|
+
pdfData = {
|
|
961
970
|
aiSummary: aiAnalysis?.executiveSummary || null,
|
|
962
971
|
groupStructure: (() => {
|
|
963
972
|
const gs = aiAnalysis?.groupStructure || {};
|
|
964
|
-
//
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
973
|
+
// Combine AI subsidiaries with real data
|
|
974
|
+
const pappersSubs = (subsidiariesData || [])
|
|
975
|
+
.filter(s => s.ca && s.ca > 0)
|
|
976
|
+
.sort((a, b) => (b.ca || 0) - (a.ca || 0))
|
|
977
|
+
.slice(0, 7)
|
|
978
|
+
.map(s => ({ entity: s.name, revenue: `${(s.ca / 1e6).toFixed(1)} M€${s.annee ? ' (' + s.annee + ')' : ''}` }));
|
|
979
|
+
|
|
980
|
+
if (pappersSubs.length > 0) {
|
|
981
|
+
gs.subsidiaries = pappersSubs;
|
|
971
982
|
}
|
|
972
983
|
return gs;
|
|
973
984
|
})(),
|
|
974
985
|
aiCompetitors: aiAnalysis?.competitors || [],
|
|
975
|
-
maHistory: aiAnalysis?.maHistory || [],
|
|
986
|
+
maHistory: (aiAnalysis?.maHistory?.length ? aiAnalysis.maHistory : codeBuiltMaHistory) || [],
|
|
976
987
|
riskAssessment: aiAnalysis?.riskAssessment || null,
|
|
977
988
|
healthScore: aiAnalysis?.healthScore || null,
|
|
978
989
|
growthAnalysis: (() => {
|
|
@@ -1203,7 +1214,7 @@ OBLIGATOIRE :
|
|
|
1203
1214
|
siren: r.siren,
|
|
1204
1215
|
})),
|
|
1205
1216
|
// Etablissements
|
|
1206
|
-
etablissements: (etablissements || []).map(e => ({
|
|
1217
|
+
etablissements: (etablissements || []).filter(e => e.actif !== false).map(e => ({
|
|
1207
1218
|
siret: e.siret,
|
|
1208
1219
|
type: e.type,
|
|
1209
1220
|
address: e.adresse,
|
|
@@ -1247,7 +1258,7 @@ OBLIGATOIRE :
|
|
|
1247
1258
|
bodacc: (bodacc || []).slice(0, 15).map(b => ({
|
|
1248
1259
|
date: b.date || '—',
|
|
1249
1260
|
type: b.type || '—',
|
|
1250
|
-
description: b.description || '',
|
|
1261
|
+
description: (b.description && b.description.length > 140) ? b.description.substring(0, 140) + '...' : (b.description || ''),
|
|
1251
1262
|
url: b.url || null,
|
|
1252
1263
|
})),
|
|
1253
1264
|
// Procédures collectives
|
|
@@ -1307,7 +1318,7 @@ OBLIGATOIRE :
|
|
|
1307
1318
|
financialHistory,
|
|
1308
1319
|
subsidiaries: subsidiariesData,
|
|
1309
1320
|
aiAnalysis,
|
|
1310
|
-
groupStructure:
|
|
1321
|
+
groupStructure: aiAnalysis?.groupStructure,
|
|
1311
1322
|
summary: `${identity.name || siren} — ${identity.formeJuridique || ''}, ${identity.nafLabel || ''}. Created ${identity.dateCreation || '?'}.`,
|
|
1312
1323
|
executiveSummary: aiAnalysis?.executiveSummary,
|
|
1313
1324
|
strengths: aiAnalysis?.strengths || [],
|
|
@@ -1319,17 +1330,16 @@ OBLIGATOIRE :
|
|
|
1319
1330
|
language: getLanguage()
|
|
1320
1331
|
};
|
|
1321
1332
|
|
|
1322
|
-
const
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
}
|
|
1331
|
-
|
|
1332
|
-
}
|
|
1333
|
+
const result = await handleExport(options.export, profileData, {
|
|
1334
|
+
pdfData: pdfData,
|
|
1335
|
+
output: options.output,
|
|
1336
|
+
commandType: 'profile',
|
|
1337
|
+
pdfOptions: {
|
|
1338
|
+
type: 'intel-report',
|
|
1339
|
+
title: `Profile — ${identity.name || siren}`,
|
|
1340
|
+
},
|
|
1341
|
+
});
|
|
1342
|
+
console.log(chalk.green(`\n ✅ ${result}\n`));
|
|
1333
1343
|
} catch (e) {
|
|
1334
1344
|
console.error(chalk.red(`\n ❌ Export failed: ${e.message}\n`));
|
|
1335
1345
|
}
|
package/src/commands/report.js
CHANGED
|
@@ -9,7 +9,7 @@ import { diffCompetitorSnapshots } from '../trackers/competitor.js';
|
|
|
9
9
|
import { diffKeywordSnapshots } from '../trackers/keyword.js';
|
|
10
10
|
import { diffBrandSnapshots } from '../trackers/brand.js';
|
|
11
11
|
import { computeThreatScore } from '../trackers/competitor.js';
|
|
12
|
-
import {
|
|
12
|
+
import { handleExport, formatForExport } from '../utils/export.js';
|
|
13
13
|
import { setLanguage, getLanguage } from '../utils/i18n.js';
|
|
14
14
|
|
|
15
15
|
export async function runReport(options = {}) {
|
|
@@ -87,20 +87,18 @@ export async function runReport(options = {}) {
|
|
|
87
87
|
console.log(content);
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
// ── Export raw data
|
|
90
|
+
// ── Export raw data (json, csv, xls, pdf) ──────────────────────────────────
|
|
91
91
|
if (options.export) {
|
|
92
92
|
try {
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
console.log(chalk.yellow(`\n ⚠️ Unsupported export format: ${options.export}. Use 'json' or 'csv'.\n`));
|
|
103
|
-
}
|
|
93
|
+
const result = await handleExport(options.export, reportData, {
|
|
94
|
+
output: options.output,
|
|
95
|
+
commandType: 'report',
|
|
96
|
+
pdfOptions: {
|
|
97
|
+
type: 'intel-report',
|
|
98
|
+
title: 'IntelWatch Intelligence Report',
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
console.log(chalk.green(`\n ✅ ${result}\n`));
|
|
104
102
|
} catch (e) {
|
|
105
103
|
console.error(chalk.red(`\n ❌ Export failed: ${e.message}\n`));
|
|
106
104
|
}
|
package/src/index.js
CHANGED
|
@@ -12,6 +12,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
14
|
import { runMA } from './commands/profile.js';
|
|
15
|
+
import { saveLicenseKey, isPro, _resetCache } from './license.js';
|
|
16
|
+
import chalk from 'chalk';
|
|
17
|
+
|
|
18
|
+
// Register company data providers (Pappers, OpenCorporates, Clearbit, Apollo)
|
|
19
|
+
import './providers/index.js';
|
|
15
20
|
|
|
16
21
|
const program = new Command();
|
|
17
22
|
|
|
@@ -81,7 +86,7 @@ program
|
|
|
81
86
|
.command('check')
|
|
82
87
|
.description('Run checks for all (or one) tracker(s)')
|
|
83
88
|
.option('--tracker <id>', 'Only check this specific tracker')
|
|
84
|
-
.option('--export <format>', 'Export results (json, csv)')
|
|
89
|
+
.option('--export <format>', 'Export results (json, csv, xls, pdf)')
|
|
85
90
|
.option('--output <file>', 'Output file path for export')
|
|
86
91
|
.action(async (options) => {
|
|
87
92
|
await runCheck(options);
|
|
@@ -92,7 +97,7 @@ program
|
|
|
92
97
|
program
|
|
93
98
|
.command('digest')
|
|
94
99
|
.description('Show a summary of all changes across all trackers')
|
|
95
|
-
.option('--export <format>', 'Export results (json, csv)')
|
|
100
|
+
.option('--export <format>', 'Export results (json, csv, xls, pdf)')
|
|
96
101
|
.option('--output <file>', 'Output file path for export')
|
|
97
102
|
.action(async (options) => {
|
|
98
103
|
await runDigest(options);
|
|
@@ -115,7 +120,7 @@ program
|
|
|
115
120
|
.description('Generate a full intelligence report')
|
|
116
121
|
.option('--format <format>', 'Output format: md, html, json', 'md')
|
|
117
122
|
.option('--output <file>', 'Write report to file')
|
|
118
|
-
.option('--export <format>', 'Export raw data (json, csv)')
|
|
123
|
+
.option('--export <format>', 'Export raw data (json, csv, xls, pdf)')
|
|
119
124
|
.action(async (options) => {
|
|
120
125
|
await runReport(options);
|
|
121
126
|
});
|
|
@@ -170,7 +175,7 @@ program
|
|
|
170
175
|
.option('--ai', 'Generate an AI-powered due diligence summary (requires AI API key)')
|
|
171
176
|
.option('--format <type>', 'Output format: terminal (default) or pdf')
|
|
172
177
|
.option('--output <path>', 'Output file path for PDF')
|
|
173
|
-
.option('--export <format>', 'Export structured data (json, csv)')
|
|
178
|
+
.option('--export <format>', 'Export structured data (json, csv, xls, pdf)')
|
|
174
179
|
.action(async (sirenOrName, options) => {
|
|
175
180
|
await runMA(sirenOrName, options);
|
|
176
181
|
});
|
|
@@ -200,4 +205,25 @@ program
|
|
|
200
205
|
}
|
|
201
206
|
});
|
|
202
207
|
|
|
208
|
+
// ─── auth ─────────────────────────────────────────────────────────────────────
|
|
209
|
+
|
|
210
|
+
program
|
|
211
|
+
.command('auth <key>')
|
|
212
|
+
.description('Activate Pro license')
|
|
213
|
+
.action((key) => {
|
|
214
|
+
try {
|
|
215
|
+
const file = saveLicenseKey(key);
|
|
216
|
+
_resetCache();
|
|
217
|
+
if (isPro()) {
|
|
218
|
+
console.log(chalk.green(' ✅ Pro license activated!'));
|
|
219
|
+
console.log(chalk.gray(` Saved to ${file}`));
|
|
220
|
+
} else {
|
|
221
|
+
console.log(chalk.red(' ❌ License key appears invalid.'));
|
|
222
|
+
}
|
|
223
|
+
} catch (err) {
|
|
224
|
+
console.error(chalk.red(` ❌ ${err.message}`));
|
|
225
|
+
process.exit(1);
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
|
|
203
229
|
export { program };
|