fullstackgtm 0.50.1 → 0.52.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.
Files changed (45) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/dist/cli/auth.js +42 -11
  3. package/dist/cli/enrich.js +122 -56
  4. package/dist/cli/help.js +10 -7
  5. package/dist/cli/icp.js +155 -1
  6. package/dist/cli/init.js +3 -3
  7. package/dist/cli/tam.d.ts +1 -1
  8. package/dist/cli/tam.js +4 -1
  9. package/dist/connectors/clay.d.ts +33 -0
  10. package/dist/connectors/clay.js +123 -0
  11. package/dist/connectors/prospectSources.d.ts +12 -0
  12. package/dist/connectors/prospectSources.js +1 -1
  13. package/dist/contactProviders.d.ts +44 -0
  14. package/dist/contactProviders.js +100 -0
  15. package/dist/enrich.d.ts +7 -1
  16. package/dist/enrich.js +46 -1
  17. package/dist/icp.d.ts +2 -0
  18. package/dist/icp.js +53 -0
  19. package/dist/icpDerive.d.ts +51 -0
  20. package/dist/icpDerive.js +146 -0
  21. package/dist/index.d.ts +3 -0
  22. package/dist/index.js +3 -0
  23. package/dist/init.d.ts +1 -1
  24. package/dist/init.js +1 -1
  25. package/dist/llm.d.ts +4 -0
  26. package/dist/llm.js +77 -6
  27. package/dist/publicHttp.js +6 -0
  28. package/docs/api.md +18 -1
  29. package/package.json +1 -1
  30. package/src/cli/auth.ts +37 -10
  31. package/src/cli/enrich.ts +130 -55
  32. package/src/cli/help.ts +10 -7
  33. package/src/cli/icp.ts +144 -3
  34. package/src/cli/init.ts +3 -3
  35. package/src/cli/tam.ts +4 -2
  36. package/src/connectors/clay.ts +155 -0
  37. package/src/connectors/prospectSources.ts +7 -1
  38. package/src/contactProviders.ts +141 -0
  39. package/src/enrich.ts +51 -2
  40. package/src/icp.ts +55 -0
  41. package/src/icpDerive.ts +158 -0
  42. package/src/index.ts +38 -0
  43. package/src/init.ts +2 -2
  44. package/src/llm.ts +71 -6
  45. package/src/publicHttp.ts +7 -1
package/src/index.ts CHANGED
@@ -1,4 +1,17 @@
1
1
  export { auditSnapshot, defaultPolicy } from "./audit.ts";
2
+ export {
3
+ DEFAULT_ICP_DERIVATION_MODEL,
4
+ OPENROUTER_API_BASE,
5
+ deriveWebsiteIcp,
6
+ normalizeCompanyWebsite,
7
+ websiteText,
8
+ icpReviewSegments,
9
+ type WebsiteIcpDerivation,
10
+ type WebsiteIcpEvidence,
11
+ type IcpReviewSegment,
12
+ type IcpDerivationProgress,
13
+ } from "./icpDerive.ts";
14
+
2
15
  export {
3
16
  acquireCheckpointId,
4
17
  acquireCheckpointsDir,
@@ -55,6 +68,20 @@ export {
55
68
  type RulePackageTrust,
56
69
  } from "./config.ts";
57
70
  export { applyPatchPlan, type ApplyPatchPlanOptions } from "./connector.ts";
71
+ export {
72
+ CONTACT_PROVIDER_CAPABILITIES,
73
+ runContactWaterfall,
74
+ validateContactWaterfall,
75
+ type ContactField,
76
+ type ContactInputShape,
77
+ type ContactProviderAdapter,
78
+ type ContactProviderBilling,
79
+ type ContactProviderCapability,
80
+ type ContactProviderExecution,
81
+ type ContactWaterfallAttempt,
82
+ type ContactWaterfallResult,
83
+ type ContactWaterfallStep,
84
+ } from "./contactProviders.ts";
58
85
  export {
59
86
  APPLY_STAGES,
60
87
  BACKFILL_STRIPE_STAGES,
@@ -70,6 +97,17 @@ export {
70
97
  type ProgressSnapshot,
71
98
  } from "./progress.ts";
72
99
  export { createHubspotConnector, type HubspotConnectorOptions } from "./connectors/hubspot.ts";
100
+ export {
101
+ CLAY_PUBLIC_API_BASE,
102
+ clayApiKey,
103
+ createClaySearch,
104
+ normalizeClayPerson,
105
+ runClayPeopleSearchPage,
106
+ validateClayApiKey,
107
+ type ClayPeopleSearchPage,
108
+ type ClaySearchSourceType,
109
+ type ClayKeyValidation,
110
+ } from "./connectors/clay.ts";
73
111
  export {
74
112
  DEFAULT_LOOPBACK_PORT,
75
113
  DEFAULT_OAUTH_SCOPES,
package/src/init.ts CHANGED
@@ -19,7 +19,7 @@ import { builtinAcquirePreset, ENRICH_CONFIG_FILE_NAME, type EnrichConfig } from
19
19
  import { DEFAULT_FIT_THRESHOLD, type Icp } from "./icp.ts";
20
20
 
21
21
  export type InitProvider = "hubspot" | "salesforce";
22
- export type InitSource = "pipe0" | "explorium" | "linkedin";
22
+ export type InitSource = "pipe0" | "explorium" | "clay" | "linkedin";
23
23
 
24
24
  export type ScaffoldOptions = {
25
25
  /** discovery source the acquire preset + playbook are wired for. Default "pipe0". */
@@ -63,7 +63,7 @@ export function starterIcp(): Icp {
63
63
  export function starterEnrichConfig(source: InitSource): EnrichConfig {
64
64
  const preset = builtinAcquirePreset(source);
65
65
  if (!preset?.acquire) {
66
- // builtinAcquirePreset covers pipe0/explorium/linkedin, so this is unreachable
66
+ // builtinAcquirePreset covers pipe0/explorium/clay/linkedin, so this is unreachable
67
67
  // for the typed InitSource set — guard anyway rather than emit a broken file.
68
68
  throw new Error(`init: no acquire preset for source "${source}"`);
69
69
  }
package/src/llm.ts CHANGED
@@ -119,6 +119,10 @@ export type LlmCallOptions = {
119
119
  * endpoint). Origin → `/v1/chat/completions` appended; a base ending in
120
120
  * `/chat/completions` is used verbatim. Threaded from `OPENAI_API_BASE_URL`. */
121
121
  openaiBaseUrl?: string;
122
+ /** Optional OpenAI-compatible streaming telemetry. Raw reasoning text is
123
+ * deliberately not exposed; only provider-authored summaries and activity. */
124
+ onReasoningSummary?: (summary: string) => void;
125
+ onReasoningActivity?: (receivedCharacters: number) => void;
122
126
  };
123
127
 
124
128
  export type LlmExtractedInsight = ExtractedCallInsight & {
@@ -448,15 +452,19 @@ export async function forcedToolCall(
448
452
  return block.input;
449
453
  }
450
454
  const openaiUrl = resolveLlmUrl(options.openaiBaseUrl, OPENAI_URL, "/v1/chat/completions");
455
+ const requestBody = {
456
+ model,
457
+ messages: [{ role: "user", content: prompt }],
458
+ tools: [{ type: "function", function: { name: toolName, parameters: schema } }],
459
+ tool_choice: { type: "function", function: { name: toolName } },
460
+ };
461
+ if (options.onReasoningSummary || options.onReasoningActivity) {
462
+ return streamOpenAiToolCall(fetchImpl, openaiUrl, requestBody, options);
463
+ }
451
464
  const response = await llmFetch(fetchImpl, openaiUrl, {
452
465
  method: "POST",
453
466
  headers: { Authorization: `Bearer ${options.apiKey}`, "Content-Type": "application/json" },
454
- body: JSON.stringify({
455
- model,
456
- messages: [{ role: "user", content: prompt }],
457
- tools: [{ type: "function", function: { name: toolName, parameters: schema } }],
458
- tool_choice: { type: "function", function: { name: toolName } },
459
- }),
467
+ body: JSON.stringify(requestBody),
460
468
  });
461
469
  const call = (response as { choices?: Array<{ message?: { tool_calls?: Array<{ function?: { arguments?: string } }> } }> })
462
470
  .choices?.[0]?.message?.tool_calls?.[0];
@@ -464,6 +472,63 @@ export async function forcedToolCall(
464
472
  return JSON.parse(call.function.arguments);
465
473
  }
466
474
 
475
+ async function streamOpenAiToolCall(
476
+ fetchImpl: typeof fetch,
477
+ url: string,
478
+ body: object,
479
+ options: LlmCallOptions,
480
+ ): Promise<unknown> {
481
+ let response: Response;
482
+ try {
483
+ response = await fetchImpl(url, {
484
+ method: "POST",
485
+ headers: { Authorization: `Bearer ${options.apiKey}`, "Content-Type": "application/json" },
486
+ body: JSON.stringify({ ...body, stream: true, reasoning: { enabled: true, exclude: false } }),
487
+ });
488
+ } catch (error) {
489
+ const cause = error instanceof Error && error.cause instanceof Error ? `: ${error.cause.message}` : "";
490
+ throw new Error(`Cannot reach ${new URL(url).hostname}${cause}. Check network access.`);
491
+ }
492
+ if (!response.ok) throw new Error(`LLM API error ${response.status} ${response.statusText} from ${new URL(url).hostname}. Check the API key and model name.`);
493
+ if (!response.body) throw new Error("LLM API returned no streaming response body.");
494
+ const reader = response.body.getReader();
495
+ const decoder = new TextDecoder();
496
+ let buffer = "";
497
+ let argumentsJson = "";
498
+ let reasoningCharacters = 0;
499
+ let lastActivityReport = 0;
500
+ const seenSummaries = new Set<string>();
501
+ while (true) {
502
+ const { value, done } = await reader.read();
503
+ buffer += decoder.decode(value, { stream: !done });
504
+ const lines = buffer.split("\n");
505
+ buffer = lines.pop() ?? "";
506
+ for (const line of lines) {
507
+ if (!line.startsWith("data:")) continue;
508
+ const data = line.slice(5).trim();
509
+ if (!data || data === "[DONE]") continue;
510
+ let chunk: { choices?: Array<{ delta?: { reasoning?: string; reasoning_details?: Array<{ type?: string; summary?: string; text?: string }>; tool_calls?: Array<{ function?: { arguments?: string } }> } }> };
511
+ try { chunk = JSON.parse(data); } catch { continue; }
512
+ const delta = chunk.choices?.[0]?.delta;
513
+ argumentsJson += delta?.tool_calls?.[0]?.function?.arguments ?? "";
514
+ reasoningCharacters += delta?.reasoning?.length ?? 0;
515
+ for (const detail of delta?.reasoning_details ?? []) {
516
+ reasoningCharacters += detail.text?.length ?? detail.summary?.length ?? 0;
517
+ if (detail.type !== "reasoning.summary" || !detail.summary) continue;
518
+ const summary = detail.summary.replace(/\s+/g, " ").trim();
519
+ if (summary && !seenSummaries.has(summary)) { seenSummaries.add(summary); options.onReasoningSummary?.(summary); }
520
+ }
521
+ if (reasoningCharacters - lastActivityReport >= 800) {
522
+ lastActivityReport = reasoningCharacters;
523
+ options.onReasoningActivity?.(reasoningCharacters);
524
+ }
525
+ }
526
+ if (done) break;
527
+ }
528
+ if (!argumentsJson) throw new Error("OpenAI-compatible model returned no streamed tool call — try again or a different --model.");
529
+ return JSON.parse(argumentsJson);
530
+ }
531
+
467
532
  async function llmFetch(fetchImpl: typeof fetch, url: string, init: RequestInit): Promise<unknown> {
468
533
  let response: Response;
469
534
  try {
package/src/publicHttp.ts CHANGED
@@ -84,9 +84,15 @@ export type PublicRequestHop = (url: URL, addresses: PublicAddress[], headers: R
84
84
 
85
85
  const requestHop: PublicRequestHop = (url, addresses, headers, timeoutMs, maxBytes) => new Promise((resolve, reject) => {
86
86
  let cursor = 0;
87
- const options: RequestOptions = {
87
+ const options: RequestOptions & { autoSelectFamily?: boolean } = {
88
88
  protocol: url.protocol, hostname: url.hostname, port: url.port || undefined,
89
89
  path: `${url.pathname}${url.search}`, method: "GET", headers,
90
+ // Node 20+ enables family autoselection by default and calls custom lookup
91
+ // functions with `{ all: true }`. This transport already resolved and
92
+ // validated every address, then pins one exact address per socket attempt;
93
+ // disable the second selection layer so the callback keeps the classic
94
+ // single-address contract and can never fall back to system DNS.
95
+ autoSelectFamily: false,
90
96
  lookup: (_hostname, options, callback) => {
91
97
  const requestedFamily = typeof options === "number" ? options : options?.family;
92
98
  const eligible = requestedFamily ? addresses.filter((a) => a.family === requestedFamily) : addresses;