indiecrm-cli 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/CHANGELOG.md +447 -0
  2. package/CODE_OF_CONDUCT.md +35 -0
  3. package/CONTRIBUTING.md +7 -0
  4. package/README.md +14 -2
  5. package/RESEARCH.md +346 -0
  6. package/SECURITY.md +35 -0
  7. package/dist/affiliate-copy.js +49 -0
  8. package/dist/auth.js +453 -0
  9. package/dist/bigquery.js +199 -0
  10. package/dist/chrome-browser.js +231 -0
  11. package/dist/cli.js +19652 -0
  12. package/dist/company-identity-review.js +78 -0
  13. package/dist/company-leads.js +422 -0
  14. package/dist/company-recovery.js +84 -0
  15. package/dist/deel-outreach.js +469 -0
  16. package/dist/deel-salesnav.js +368 -0
  17. package/dist/direct-path.js +326 -0
  18. package/dist/domain.js +53 -0
  19. package/dist/domainfinder.js +764 -0
  20. package/dist/engine.js +216 -0
  21. package/dist/historical-queries.js +189 -0
  22. package/dist/hunter-emailfinder.js +252 -0
  23. package/dist/icp-templates.js +171 -0
  24. package/dist/indiecrm/commands.js +108 -0
  25. package/dist/indiecrm-cli.js +2 -104
  26. package/dist/instantly.js +136 -0
  27. package/dist/io.js +21 -0
  28. package/dist/leadlists-funnel.js +148 -0
  29. package/dist/linkedin-companies.js +562 -0
  30. package/dist/linkedin-product-details.js +1203 -0
  31. package/dist/linkedin-product-search.js +1081 -0
  32. package/dist/linkedin-products.js +786 -0
  33. package/dist/linkedin-session-contracts.js +3 -0
  34. package/dist/linkedin-session.js +846 -0
  35. package/dist/providers.js +1 -0
  36. package/dist/research-browser-preference.js +37 -0
  37. package/dist/sales-navigator.js +1231 -0
  38. package/dist/salesnav-backfill.js +710 -0
  39. package/dist/sample-data.js +34 -0
  40. package/dist/session-recovery.js +62 -0
  41. package/dist/vendor/salesprompter-shared/extension-session-contracts.js +29 -0
  42. package/dist/vendor/salesprompter-shared/linkedin-session.js +22 -0
  43. package/dist/vendor/salesprompter-shared/phantombuster-contracts.js +16 -0
  44. package/dist/vendor/salesprompter-shared/session-vault-contracts.js +17 -0
  45. package/package.json +73 -14
@@ -0,0 +1,148 @@
1
+ function marketCountries(market) {
2
+ if (market === "dach") {
3
+ return ["DE", "AT", "CH"];
4
+ }
5
+ if (market === "europe") {
6
+ return ["DE", "AT", "CH", "NL", "GB", "FR", "SE", "DK"];
7
+ }
8
+ return [];
9
+ }
10
+ function marketCountrySql(market) {
11
+ const countries = marketCountries(market);
12
+ if (countries.length === 0) {
13
+ return null;
14
+ }
15
+ return countries.map((country) => `"${country}"`).join(", ");
16
+ }
17
+ function marketQueryTerms(market) {
18
+ if (market === "dach") {
19
+ return ["germany", "austria", "switzerland", "deutschland", "schweiz", "österreich"];
20
+ }
21
+ if (market === "europe") {
22
+ return ["germany", "austria", "switzerland", "netherlands", "united kingdom", "france", "sweden", "denmark"];
23
+ }
24
+ return [];
25
+ }
26
+ function encodedLikeClause(queryTerms) {
27
+ if (queryTerms.length === 0) {
28
+ return "TRUE";
29
+ }
30
+ const clauses = queryTerms.map((term) => `LOWER(CAST(query AS STRING)) LIKE "%${term.replaceAll('"', '%%')}%"`);
31
+ return `(${clauses.join(" OR ")})`;
32
+ }
33
+ export function buildLeadlistsFunnelQueries(vendor, market) {
34
+ if (vendor !== "deel") {
35
+ throw new Error(`Unsupported funnel vendor: ${vendor}`);
36
+ }
37
+ const countries = marketCountrySql(market);
38
+ const queryTerms = marketQueryTerms(market);
39
+ const marketLikeClause = encodedLikeClause(queryTerms);
40
+ const countryInClause = countries === null ? "TRUE" : `c.countryCode IN (${countries})`;
41
+ const countryWithDomainClause = countries === null ? "c.domain IS NOT NULL" : `c.countryCode IN (${countries}) AND c.domain IS NOT NULL`;
42
+ const companyMarketClause = countries === null ? "TRUE" : `e.countryCode IN (${countries})`;
43
+ const companyUsableDomainClause = countries === null
44
+ ? "e.domain IS NOT NULL AND COALESCE(e.domainBlacklisted, FALSE) = FALSE"
45
+ : `e.countryCode IN (${countries}) AND e.domain IS NOT NULL AND COALESCE(e.domainBlacklisted, FALSE) = FALSE`;
46
+ const companyDomainNotFoundClause = countries === null
47
+ ? "COALESCE(e.company_emailDomainNotFound, FALSE) = TRUE"
48
+ : `e.countryCode IN (${countries}) AND COALESCE(e.company_emailDomainNotFound, FALSE) = TRUE`;
49
+ const downstreamCountryClause = countries === null ? "TRUE" : `UPPER(CAST(company_countryCode AS STRING)) IN (${countries})`;
50
+ return {
51
+ queryTerms,
52
+ stages: [
53
+ {
54
+ key: "leadlist_intent_hr_sales_people_all",
55
+ description: "All Sales Navigator people lead-list definitions with HR function filter.",
56
+ sql: `SELECT COUNT(*) AS row_count, COUNT(DISTINCT leadListId) AS distinct_leadlists, COUNT(DISTINCT CAST(query AS STRING)) AS distinct_queries
57
+ FROM \`icpidentifier.SalesPrompter.leadLists_raw\`
58
+ WHERE query IS NOT NULL
59
+ AND LOWER(CAST(query AS STRING)) LIKE "%/sales/search/people%"
60
+ AND LOWER(CAST(query AS STRING)) LIKE "%type%3afunction%2cvalues%3alist((id%3a12%"`
61
+ },
62
+ {
63
+ key: "leadlist_intent_hr_sales_people_market_hints",
64
+ description: "HR Sales Navigator people lead-list definitions whose query text references the selected market.",
65
+ sql: `SELECT COUNT(*) AS row_count, COUNT(DISTINCT leadListId) AS distinct_leadlists, COUNT(DISTINCT CAST(query AS STRING)) AS distinct_queries
66
+ FROM \`icpidentifier.SalesPrompter.leadLists_raw\`
67
+ WHERE query IS NOT NULL
68
+ AND LOWER(CAST(query AS STRING)) LIKE "%/sales/search/people%"
69
+ AND LOWER(CAST(query AS STRING)) LIKE "%type%3afunction%2cvalues%3alist((id%3a12%"
70
+ AND ${marketLikeClause}`
71
+ },
72
+ {
73
+ key: "people_export_raw",
74
+ description: "Raw people export coverage before downstream enrichment.",
75
+ sql: `SELECT COUNT(*) AS row_count, COUNT(DISTINCT contactId) AS distinct_contacts, COUNTIF(company IS NOT NULL) AS with_company_name, COUNTIF(companyUrl IS NOT NULL) AS with_company_url, COUNTIF(firstName IS NOT NULL) AS with_first_name, COUNTIF(lastName IS NOT NULL) AS with_last_name
76
+ FROM \`icpidentifier.SalesPrompter.linkedinSearchExport_people_unique\``
77
+ },
78
+ {
79
+ key: "people_export_company_join_market",
80
+ description: "People exports whose company URL can be resolved to a LinkedIn company record in the selected market.",
81
+ sql: `WITH people AS (
82
+ SELECT companyUrl, firstName, lastName, REGEXP_EXTRACT(companyUrl, "company/([0-9]+)") AS companyIdFromUrl
83
+ FROM \`icpidentifier.SalesPrompter.linkedinSearchExport_people_unique\`
84
+ WHERE companyUrl IS NOT NULL
85
+ ), companies AS (
86
+ SELECT CAST(id AS STRING) AS companyId, handle, name, domain, countryCode
87
+ FROM \`icpidentifier.SalesPrompter.linkedin_companies\`
88
+ )
89
+ SELECT
90
+ COUNT(*) AS people_with_company_url,
91
+ COUNTIF(companyIdFromUrl IS NOT NULL) AS people_with_extractable_company_id,
92
+ COUNTIF(c.companyId IS NOT NULL) AS people_joined_to_company,
93
+ COUNTIF(${countryInClause}) AS people_in_market_company,
94
+ COUNTIF(${countryWithDomainClause}) AS people_in_market_company_with_domain,
95
+ COUNTIF(${countryInClause} AND p.firstName IS NOT NULL) AS people_in_market_with_first_name,
96
+ COUNTIF(${countryInClause} AND p.lastName IS NOT NULL) AS people_in_market_with_last_name
97
+ FROM people p
98
+ LEFT JOIN companies c ON p.companyIdFromUrl = c.companyId`
99
+ },
100
+ {
101
+ key: "company_search_hr_department_market",
102
+ description: "Company search rows with HR department-headcount filter and company/domain coverage in the selected market.",
103
+ sql: `WITH hr_company_search AS (
104
+ SELECT companyId, leadListId, companyUrl, regularCompanyUrl, companyName
105
+ FROM \`icpidentifier.SalesPrompter.salesNavigatorSearchExport_companies_unique\`
106
+ WHERE query IS NOT NULL
107
+ AND LOWER(CAST(query AS STRING)) LIKE "%selectedsubfilter%3a12%"
108
+ ), enriched AS (
109
+ SELECT companyId, leadListId, handle, name, domain, countryCode, companySize, hunter_emailCount, domainBlacklisted, company_emailDomainNotFound
110
+ FROM \`icpidentifier.SalesPrompter.salesNavigatorSearchExport_companies_unique_enriched\`
111
+ )
112
+ SELECT
113
+ COUNT(*) AS company_rows,
114
+ COUNT(DISTINCT CONCAT(CAST(h.companyId AS STRING),"|",CAST(h.leadListId AS STRING))) AS distinct_company_search_rows,
115
+ COUNTIF(h.companyUrl IS NOT NULL OR h.regularCompanyUrl IS NOT NULL) AS with_linkedin_company_profile,
116
+ COUNTIF(e.handle IS NOT NULL) AS with_handle,
117
+ COUNTIF(${companyMarketClause}) AS in_market,
118
+ COUNTIF(${countries === null ? "e.domain IS NOT NULL" : `e.countryCode IN (${countries}) AND e.domain IS NOT NULL`}) AS with_domain,
119
+ COUNTIF(${companyUsableDomainClause}) AS with_usable_domain,
120
+ COUNTIF(${companyDomainNotFoundClause}) AS domain_not_found
121
+ FROM hr_company_search h
122
+ LEFT JOIN enriched e
123
+ ON CAST(h.companyId AS STRING) = CAST(e.companyId AS STRING)
124
+ AND CAST(h.leadListId AS STRING) = CAST(e.leadListId AS STRING)`
125
+ },
126
+ {
127
+ key: "downstream_contact_snapshot_market_hr_function",
128
+ description: "Downstream HR contact snapshot with cleaned names and email fields in the selected market.",
129
+ sql: `SELECT
130
+ COUNT(*) AS row_count,
131
+ COUNTIF(domain IS NOT NULL) AS with_domain,
132
+ COUNTIF(firstName_cleaned IS NOT NULL) AS with_first_name_cleaned,
133
+ COUNTIF(lastName_cleaned IS NOT NULL) AS with_last_name_cleaned,
134
+ COUNTIF(email IS NOT NULL) AS with_email,
135
+ COUNTIF(email IS NOT NULL AND COALESCE(email_invalid, FALSE) = FALSE) AS with_valid_email
136
+ FROM \`icpidentifier.SalesGPT.leadPool_new\`
137
+ WHERE CAST(functionId AS STRING) = "12"
138
+ AND ${downstreamCountryClause}`
139
+ }
140
+ ],
141
+ notes: [
142
+ "leadPool_new is downstream and filtered; it should not be used as the market-size source of truth.",
143
+ "linkedinSearchExport_people_unique can be joined to linkedin_companies through companyUrl -> company id extraction.",
144
+ "linkedinSearchExport_people_unique does not currently join directly to cleaned-name or email tables on contactId.",
145
+ "The cleaned-name and email stage is therefore reported as a downstream snapshot, not as a strict continuation of the raw people-export funnel."
146
+ ]
147
+ };
148
+ }