salesprompter-cli 0.1.81 → 0.1.82
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/README.md +2 -0
- package/dist/chrome-browser.js +49 -4
- package/dist/cli.js +6 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -64,6 +64,8 @@ Research runs reuse that profile in background Chrome, execute the GET requests
|
|
|
64
64
|
|
|
65
65
|
The default `--browser session` keeps the extension-credential route. Credentials load only for pending requests; on an auth failure it tries changed credentials for the same identity once. Add `--wait-for-session 300` for a bounded refresh wait. The optional Codex `--browser-relay-port` route still needs its external worker. Salesprompter workspace authentication applies in every mode.
|
|
66
66
|
|
|
67
|
+
Ctrl+C, SIGTERM and terminal hangup stop Chrome research gracefully: completed searches stay saved, the owned browser closes, and the research lock is released before exit. Repeat the same command to resume only pending work. A forced kill or power loss cannot run cleanup; if a stale lock remains, confirm that its process stopped before removing only `.research-lock` as the CLI error explains.
|
|
68
|
+
|
|
67
69
|
Create a JSON brief with verified numeric LinkedIn company IDs. Use each subsidiary's own ID; names alone are not an employer match. Missing IDs stay in the coverage report as unresolved.
|
|
68
70
|
|
|
69
71
|
For a name-only list, the executable end-to-end research mode is:
|
package/dist/chrome-browser.js
CHANGED
|
@@ -12,6 +12,14 @@ export class ChromeResearchError extends Error {
|
|
|
12
12
|
this.name = "ChromeResearchError";
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
|
+
export class ChromeResearchInterruptedError extends Error {
|
|
16
|
+
signal;
|
|
17
|
+
constructor(signal) {
|
|
18
|
+
super("Research interrupted. Completed searches remain saved; rerun with the same --out-dir to resume.");
|
|
19
|
+
this.signal = signal;
|
|
20
|
+
this.name = "ChromeResearchInterruptedError";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
15
23
|
export function validateChromeResearchUrl(value) {
|
|
16
24
|
const url = new URL(value);
|
|
17
25
|
if (url.origin !== ORIGIN || url.username || url.password || url.hash ||
|
|
@@ -48,6 +56,8 @@ export async function launchResearchChrome(visible = false) {
|
|
|
48
56
|
try {
|
|
49
57
|
return await puppeteer.launch({ channel: "chrome", userDataDir: profile, pipe: true, headless: !visible,
|
|
50
58
|
timeout: 30000, protocolTimeout: 45000, defaultViewport: null,
|
|
59
|
+
// Let the command unwind its checkpoint/lock finally blocks before exiting.
|
|
60
|
+
handleSIGINT: false, handleSIGTERM: false, handleSIGHUP: false,
|
|
51
61
|
});
|
|
52
62
|
}
|
|
53
63
|
catch {
|
|
@@ -79,13 +89,34 @@ export function createChromeResearchBrowser(options = {}, launch = launchResearc
|
|
|
79
89
|
let initialization;
|
|
80
90
|
let closed = false;
|
|
81
91
|
let busy = false;
|
|
92
|
+
let browserClosing;
|
|
93
|
+
const signals = ["SIGINT", "SIGTERM", "SIGHUP"];
|
|
94
|
+
const signalHandlers = signals.map(signal => () => {
|
|
95
|
+
if (fatal instanceof ChromeResearchInterruptedError)
|
|
96
|
+
return;
|
|
97
|
+
fatal = new ChromeResearchInterruptedError(signal);
|
|
98
|
+
closed = true;
|
|
99
|
+
void closeOwnedBrowser().catch(() => { });
|
|
100
|
+
});
|
|
101
|
+
function closeOwnedBrowser() {
|
|
102
|
+
if (browser)
|
|
103
|
+
browserClosing ??= browser.close();
|
|
104
|
+
return browserClosing ?? Promise.resolve();
|
|
105
|
+
}
|
|
82
106
|
const waitMs = options.loginWaitMs ?? 30000;
|
|
83
107
|
if (!Number.isFinite(waitMs) || waitMs < 0 || waitMs > 3600000)
|
|
84
108
|
throw new Error("Chrome login wait must be between 0 and 3600 seconds.");
|
|
85
109
|
async function initialize() {
|
|
110
|
+
signals.forEach((signal, index) => process.on(signal, signalHandlers[index]));
|
|
86
111
|
browser = await launch(options.visible);
|
|
112
|
+
if (closed) {
|
|
113
|
+
await closeOwnedBrowser();
|
|
114
|
+
throw fatal ?? new ChromeResearchError("Research Chrome connection was cancelled.");
|
|
115
|
+
}
|
|
87
116
|
page = await browser.newPage();
|
|
88
117
|
page.on("response", (response) => {
|
|
118
|
+
if (fatal)
|
|
119
|
+
return;
|
|
89
120
|
const url = new URL(response.url());
|
|
90
121
|
if (url.origin !== ORIGIN)
|
|
91
122
|
return;
|
|
@@ -114,6 +145,8 @@ export function createChromeResearchBrowser(options = {}, launch = launchResearc
|
|
|
114
145
|
await page.goto(`${ORIGIN}/sales/search/people`, { waitUntil: "domcontentloaded", timeout: 30000 });
|
|
115
146
|
}
|
|
116
147
|
catch {
|
|
148
|
+
if (closed)
|
|
149
|
+
throw fatal ?? new ChromeResearchError("Research Chrome connection was cancelled.");
|
|
117
150
|
if (!browser.connected)
|
|
118
151
|
throw new ChromeResearchError("Research Chrome closed before connecting.");
|
|
119
152
|
}
|
|
@@ -121,7 +154,7 @@ export function createChromeResearchBrowser(options = {}, launch = launchResearc
|
|
|
121
154
|
let noticeAt = Date.now() + 30000;
|
|
122
155
|
while (!headers) {
|
|
123
156
|
if (closed)
|
|
124
|
-
throw new ChromeResearchError("Research Chrome connection was cancelled.");
|
|
157
|
+
throw fatal ?? new ChromeResearchError("Research Chrome connection was cancelled.");
|
|
125
158
|
if (fatal)
|
|
126
159
|
throw fatal;
|
|
127
160
|
if (!browser.connected || page.isClosed())
|
|
@@ -142,7 +175,7 @@ export function createChromeResearchBrowser(options = {}, launch = launchResearc
|
|
|
142
175
|
}
|
|
143
176
|
async function ready() {
|
|
144
177
|
if (closed)
|
|
145
|
-
throw new Error("Research Chrome is closed.");
|
|
178
|
+
throw fatal ?? new Error("Research Chrome is closed.");
|
|
146
179
|
initialization ??= initialize();
|
|
147
180
|
await initialization;
|
|
148
181
|
if (fatal)
|
|
@@ -171,7 +204,7 @@ export function createChromeResearchBrowser(options = {}, launch = launchResearc
|
|
|
171
204
|
const text = await response.text();
|
|
172
205
|
if (text.length > 50 * 1024 * 1024) throw new Error('Research response exceeded 50 MB');
|
|
173
206
|
return {status: response.status, text, redirected: response.redirected, retryAfter: response.headers.get('retry-after')};
|
|
174
|
-
})()`).catch(() => { throw new ChromeResearchError(`Chrome research request could not complete. ${LOGIN_HELP}`); });
|
|
207
|
+
})()`).catch(() => { throw fatal ?? new ChromeResearchError(`Chrome research request could not complete. ${LOGIN_HELP}`); });
|
|
175
208
|
const body = parseChromeResearchResult(result);
|
|
176
209
|
if (fatal)
|
|
177
210
|
throw fatal;
|
|
@@ -181,6 +214,18 @@ export function createChromeResearchBrowser(options = {}, launch = launchResearc
|
|
|
181
214
|
busy = false;
|
|
182
215
|
}
|
|
183
216
|
},
|
|
184
|
-
async close() {
|
|
217
|
+
async close() {
|
|
218
|
+
closed = true;
|
|
219
|
+
try {
|
|
220
|
+
await closeOwnedBrowser();
|
|
221
|
+
await initialization?.catch(() => { });
|
|
222
|
+
await closeOwnedBrowser();
|
|
223
|
+
}
|
|
224
|
+
finally {
|
|
225
|
+
signals.forEach((signal, index) => process.off(signal, signalHandlers[index]));
|
|
226
|
+
headers = null;
|
|
227
|
+
identity = undefined;
|
|
228
|
+
}
|
|
229
|
+
},
|
|
185
230
|
};
|
|
186
231
|
}
|
package/dist/cli.js
CHANGED
|
@@ -17,7 +17,7 @@ import { z } from "zod";
|
|
|
17
17
|
import { AffiliateCopyReviewSchema, renderAffiliateCopyReview } from "./affiliate-copy.js";
|
|
18
18
|
import { CompanyBriefSchema, planCompanySearches, runCompanyResearch } from "./company-leads.js";
|
|
19
19
|
import { createLazySessionRecovery, waitForFreshSession, isFreshSessionForIdentity } from "./session-recovery.js";
|
|
20
|
-
import { createChromeResearchBrowser } from "./chrome-browser.js";
|
|
20
|
+
import { createChromeResearchBrowser, ChromeResearchInterruptedError } from "./chrome-browser.js";
|
|
21
21
|
import { clearAuthSession, loginWithBrowserConnect, loginWithDeviceFlow, loginWithToken, readAuthSession, requireAuthSession, shouldBypassAuth, verifySession, writeAuthSession } from "./auth.js";
|
|
22
22
|
import { buildBigQueryLeadLookupSql, executeBigQuerySql, normalizeBigQueryLeadRows, runBigQueryQuery, runBigQueryRows } from "./bigquery.js";
|
|
23
23
|
import { AccountProfileSchema, EnrichedLeadSchema, IcpSchema, LeadSchema, ScoredLeadSchema, SyncTargetSchema } from "./domain.js";
|
|
@@ -19563,6 +19563,11 @@ async function closeGlobalHttpDispatcher() {
|
|
|
19563
19563
|
}
|
|
19564
19564
|
main()
|
|
19565
19565
|
.catch((error) => {
|
|
19566
|
+
if (error instanceof ChromeResearchInterruptedError) {
|
|
19567
|
+
process.stderr.write(runtimeOutputOptions.json ? `${JSON.stringify({ status: "interrupted", message: error.message, signal: error.signal, resumable: true })}\n` : `${error.message}\n`);
|
|
19568
|
+
process.exitCode = error.signal === "SIGINT" ? 130 : error.signal === "SIGTERM" ? 143 : 129;
|
|
19569
|
+
return;
|
|
19570
|
+
}
|
|
19566
19571
|
if (error instanceof Error &&
|
|
19567
19572
|
(error.message === "prompt cancelled" || error.message === "readline was closed")) {
|
|
19568
19573
|
process.exitCode = 130;
|
package/package.json
CHANGED