salesprompter-cli 0.1.36 → 0.1.37

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 (2) hide show
  1. package/dist/cli.js +101 -6
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawn } from "node:child_process";
3
- import { access, appendFile, mkdir, readFile, readdir, writeFile } from "node:fs/promises";
3
+ import { access, appendFile, mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
4
4
  import { createRequire } from "node:module";
5
5
  import os from "node:os";
6
6
  import path from "node:path";
@@ -136,7 +136,13 @@ const PhantombusterContainersSyncResponseSchema = z.object({
136
136
  resultsSynced: z.number().int().nonnegative(),
137
137
  outputsStored: z.number().int().nonnegative(),
138
138
  resultObjectsStored: z.number().int().nonnegative(),
139
- resultRowsStored: z.number().int().nonnegative()
139
+ resultRowsStored: z.number().int().nonnegative(),
140
+ leadListsProjected: z.number().int().nonnegative().optional(),
141
+ leadListContactsProjected: z.number().int().nonnegative().optional(),
142
+ contactsProjected: z.number().int().nonnegative().optional(),
143
+ leadPoolRows: z.number().int().nonnegative().nullable().optional(),
144
+ qualifiedContacts: z.number().int().nonnegative().nullable().optional(),
145
+ qualifiedCompanies: z.number().int().nonnegative().nullable().optional()
140
146
  });
141
147
  const CliEmailEnrichmentCompaniesResponseSchema = z.object({
142
148
  clientId: z.number().int().positive(),
@@ -4834,19 +4840,29 @@ function buildLinkedInProductCategorySalesNavigatorOutputPath(categorySlug) {
4834
4840
  return `./data/salesnav-product-category-${categorySlug}.json`;
4835
4841
  }
4836
4842
  const SALES_NAVIGATOR_TERMINAL_JOB_STATUSES = new Set(["completed", "completed_with_failures"]);
4843
+ const WORKFLOW_LOCAL_LOG_MAX_FILE_BYTES = 10 * 1024 * 1024;
4844
+ const WORKFLOW_LOCAL_LOG_MAX_LINE_BYTES = 32 * 1024;
4845
+ const WORKFLOW_LOCAL_LOG_MAX_STRING_CHARS = 4000;
4846
+ const WORKFLOW_LOCAL_LOG_MAX_ARRAY_ITEMS = 50;
4847
+ const WORKFLOW_LOCAL_LOG_MAX_OBJECT_KEYS = 50;
4848
+ const WORKFLOW_LOCAL_LOG_MAX_DEPTH = 5;
4837
4849
  function isSalesNavigatorCrawlJobTerminal(status) {
4838
4850
  return SALES_NAVIGATOR_TERMINAL_JOB_STATUSES.has(status);
4839
4851
  }
4840
4852
  function buildWorkflowTraceId(prefix) {
4841
4853
  return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
4842
4854
  }
4855
+ function buildWorkflowLogRunSuffix() {
4856
+ const timestamp = new Date().toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
4857
+ return `${timestamp}-${Math.random().toString(36).slice(2, 8)}`;
4858
+ }
4843
4859
  function buildSalesNavigatorWorkflowLogPath(input) {
4844
4860
  const slug = slugify(input) || "salesnav-product-category";
4845
- return `./data/${slug}-salesnav.log.jsonl`;
4861
+ return `./data/${slug}-${buildWorkflowLogRunSuffix()}-salesnav.log.jsonl`;
4846
4862
  }
4847
4863
  function buildSalesNavigatorCrawlLogPath(input) {
4848
4864
  const slug = slugify(input) || "salesnav-crawl";
4849
- return `./data/${slug}-crawl.log.jsonl`;
4865
+ return `./data/${slug}-${buildWorkflowLogRunSuffix()}-crawl.log.jsonl`;
4850
4866
  }
4851
4867
  function buildSalesNavigatorCrawlOutputPath(input) {
4852
4868
  const slug = slugify(input) || "salesnav-crawl";
@@ -4878,6 +4894,83 @@ function decodeSalesNavigatorQueryParam(url) {
4878
4894
  return null;
4879
4895
  }
4880
4896
  }
4897
+ function sanitizeWorkflowLogValue(value, depth = 0, seen = new WeakSet()) {
4898
+ if (typeof value === "string") {
4899
+ if (value.length <= WORKFLOW_LOCAL_LOG_MAX_STRING_CHARS) {
4900
+ return value;
4901
+ }
4902
+ return `${value.slice(0, WORKFLOW_LOCAL_LOG_MAX_STRING_CHARS)}... [truncated ${value.length - WORKFLOW_LOCAL_LOG_MAX_STRING_CHARS} chars]`;
4903
+ }
4904
+ if (typeof value !== "object" || value === null) {
4905
+ return value;
4906
+ }
4907
+ if (seen.has(value)) {
4908
+ return "[Circular]";
4909
+ }
4910
+ if (depth >= WORKFLOW_LOCAL_LOG_MAX_DEPTH) {
4911
+ return "[MaxDepth]";
4912
+ }
4913
+ seen.add(value);
4914
+ if (Array.isArray(value)) {
4915
+ const items = value
4916
+ .slice(0, WORKFLOW_LOCAL_LOG_MAX_ARRAY_ITEMS)
4917
+ .map((item) => sanitizeWorkflowLogValue(item, depth + 1, seen));
4918
+ if (value.length > WORKFLOW_LOCAL_LOG_MAX_ARRAY_ITEMS) {
4919
+ items.push({ truncatedItems: value.length - WORKFLOW_LOCAL_LOG_MAX_ARRAY_ITEMS });
4920
+ }
4921
+ return items;
4922
+ }
4923
+ const entries = Object.entries(value);
4924
+ const sanitized = {};
4925
+ for (const [key, entryValue] of entries.slice(0, WORKFLOW_LOCAL_LOG_MAX_OBJECT_KEYS)) {
4926
+ sanitized[key] = sanitizeWorkflowLogValue(entryValue, depth + 1, seen);
4927
+ }
4928
+ if (entries.length > WORKFLOW_LOCAL_LOG_MAX_OBJECT_KEYS) {
4929
+ sanitized.truncatedKeys = entries.length - WORKFLOW_LOCAL_LOG_MAX_OBJECT_KEYS;
4930
+ }
4931
+ return sanitized;
4932
+ }
4933
+ function serializeWorkflowLogEntry(entry) {
4934
+ const sanitizedEntry = {
4935
+ ...entry,
4936
+ metadata: sanitizeWorkflowLogValue(entry.metadata)
4937
+ };
4938
+ let line = JSON.stringify(sanitizedEntry);
4939
+ if (Buffer.byteLength(line, "utf8") <= WORKFLOW_LOCAL_LOG_MAX_LINE_BYTES) {
4940
+ return `${line}\n`;
4941
+ }
4942
+ const originalMetadata = entry.metadata ?? {};
4943
+ const metadataKeys = Object.keys(originalMetadata);
4944
+ line = JSON.stringify({
4945
+ ...entry,
4946
+ metadata: {
4947
+ localLogTruncated: true,
4948
+ originalMetadataKeys: metadataKeys.slice(0, WORKFLOW_LOCAL_LOG_MAX_OBJECT_KEYS),
4949
+ truncatedKeys: Math.max(0, metadataKeys.length - WORKFLOW_LOCAL_LOG_MAX_OBJECT_KEYS),
4950
+ originalMetadataBytes: Buffer.byteLength(JSON.stringify(sanitizeWorkflowLogValue(originalMetadata)), "utf8")
4951
+ }
4952
+ });
4953
+ return `${line}\n`;
4954
+ }
4955
+ async function appendWorkflowLocalLog(logPath, entry) {
4956
+ try {
4957
+ const current = await stat(logPath).catch((error) => {
4958
+ if (error.code === "ENOENT") {
4959
+ return null;
4960
+ }
4961
+ throw error;
4962
+ });
4963
+ if (current && current.size >= WORKFLOW_LOCAL_LOG_MAX_FILE_BYTES) {
4964
+ writeProgress(`[${entry.timestamp}] ${entry.event} (local log skipped because ${logPath} is already over 10 MB; durable event storage still runs when configured)`);
4965
+ return;
4966
+ }
4967
+ await appendFile(logPath, serializeWorkflowLogEntry(entry), "utf8");
4968
+ }
4969
+ catch (error) {
4970
+ const message = error instanceof Error ? error.message : String(error);
4971
+ writeProgress(`[${entry.timestamp}] workflow.local_log.write_failed: ${message}`);
4972
+ }
4973
+ }
4881
4974
  async function createWorkflowLogger(options) {
4882
4975
  const traceId = options.traceId ?? buildWorkflowTraceId("salesprompter-cli");
4883
4976
  const logPath = options.logPath;
@@ -4896,7 +4989,7 @@ async function createWorkflowLogger(options) {
4896
4989
  event,
4897
4990
  metadata
4898
4991
  };
4899
- await appendFile(logPath, `${JSON.stringify(entry)}\n`, "utf8");
4992
+ await appendWorkflowLocalLog(logPath, entry);
4900
4993
  if (eventStore) {
4901
4994
  try {
4902
4995
  await eventStore.append({
@@ -9811,6 +9904,7 @@ program
9811
9904
  .option("--mode <mode>", "Phantombuster container mode: all or finalized", "all")
9812
9905
  .option("--before-ended-at <iso>", "Only fetch containers that ended before this ISO timestamp")
9813
9906
  .option("--metadata-only", "Store container metadata without fetching output and result objects", false)
9907
+ .option("--refresh-lead-pool", "After syncing results, rebuild the Neon lead_pool_new reporting table. This can take several minutes.", false)
9814
9908
  .option("--out <path>", "Optional local JSON output path")
9815
9909
  .action(async (options) => {
9816
9910
  const agentIds = z.array(z.string().min(1)).parse(options.agentId);
@@ -9827,7 +9921,8 @@ program
9827
9921
  maxPages,
9828
9922
  mode,
9829
9923
  beforeEndedAt,
9830
- includeResults: !options.metadataOnly
9924
+ includeResults: !options.metadataOnly,
9925
+ refreshLeadPool: Boolean(options.refreshLeadPool)
9831
9926
  });
9832
9927
  const payload = {
9833
9928
  ...result,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "salesprompter-cli",
3
- "version": "0.1.36",
3
+ "version": "0.1.37",
4
4
  "description": "Sales workflow CLI for guided lead generation, enrichment, scoring, and sync.",
5
5
  "author": "Daniel Sinewe <hello@danielsinewe.com>",
6
6
  "type": "module",