gtm-now 0.10.9 → 0.10.11
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/dist/index.js +316 -54
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -11444,7 +11444,20 @@ var init_extract = __esm({
|
|
|
11444
11444
|
},
|
|
11445
11445
|
profile: {
|
|
11446
11446
|
type: "string",
|
|
11447
|
-
description: "LinkedIn URL or publicIdentifier (required for linkedin_posts)"
|
|
11447
|
+
description: "LinkedIn URL or publicIdentifier (required for linkedin_posts, single profile)"
|
|
11448
|
+
},
|
|
11449
|
+
profiles: {
|
|
11450
|
+
type: "array",
|
|
11451
|
+
items: { type: "string" },
|
|
11452
|
+
description: "Batch mode: array of LinkedIn URLs or publicIdentifiers. Checks each for recent posts concurrently. Returns lightweight results (posted/not, count, last date). Use with posted_within to filter for recent activity."
|
|
11453
|
+
},
|
|
11454
|
+
csv_file: {
|
|
11455
|
+
type: "string",
|
|
11456
|
+
description: 'Path to a CSV file containing LinkedIn profile URLs. Reads the "LinkedIn Profile" or "linkedin_url" column automatically. Alternative to profiles[] for large lists.'
|
|
11457
|
+
},
|
|
11458
|
+
concurrency: {
|
|
11459
|
+
type: "number",
|
|
11460
|
+
description: "Max concurrent API requests for batch mode (default: 5, max: 20). Higher = faster but more likely to hit rate limits."
|
|
11448
11461
|
},
|
|
11449
11462
|
company: {
|
|
11450
11463
|
type: "string",
|
|
@@ -11483,6 +11496,15 @@ var init_extract = __esm({
|
|
|
11483
11496
|
type: "string",
|
|
11484
11497
|
enum: ["summary", "full"],
|
|
11485
11498
|
description: 'Response detail level. "summary" (default) returns counts and key fields only \u2014 truncates post text, omits individual engagement items. "full" returns complete data including all post content and engager lists.'
|
|
11499
|
+
},
|
|
11500
|
+
output: {
|
|
11501
|
+
type: "string",
|
|
11502
|
+
enum: ["file", "csv", "inline"],
|
|
11503
|
+
description: 'Output mode for batch results. "csv" writes a CSV file (default for batch). "file" writes JSONL. "inline" returns in response (only for small batches).'
|
|
11504
|
+
},
|
|
11505
|
+
review: {
|
|
11506
|
+
type: "boolean",
|
|
11507
|
+
description: "Create a viewer table with batch results for human review."
|
|
11486
11508
|
}
|
|
11487
11509
|
}
|
|
11488
11510
|
}
|
|
@@ -11831,13 +11853,19 @@ var init_review = __esm({
|
|
|
11831
11853
|
items: { type: "object" },
|
|
11832
11854
|
description: "Array of row objects (for create)"
|
|
11833
11855
|
},
|
|
11834
|
-
table_id: {
|
|
11856
|
+
table_id: {
|
|
11857
|
+
type: "string",
|
|
11858
|
+
description: "Table ID (for update, get_selected, or optional client-provided ID on create)"
|
|
11859
|
+
},
|
|
11835
11860
|
rows: {
|
|
11836
11861
|
type: "array",
|
|
11837
11862
|
items: { type: "object" },
|
|
11838
11863
|
description: "Updated row objects with _row_id (for update)"
|
|
11839
11864
|
},
|
|
11840
|
-
ttl_seconds: {
|
|
11865
|
+
ttl_seconds: {
|
|
11866
|
+
type: "number",
|
|
11867
|
+
description: "Optional TTL override in seconds (default: 24h). For create only."
|
|
11868
|
+
}
|
|
11841
11869
|
},
|
|
11842
11870
|
required: ["action"]
|
|
11843
11871
|
}
|
|
@@ -13301,6 +13329,7 @@ var extract_exports = {};
|
|
|
13301
13329
|
__export(extract_exports, {
|
|
13302
13330
|
handleExtract: () => handleExtract
|
|
13303
13331
|
});
|
|
13332
|
+
import * as fs2 from "fs";
|
|
13304
13333
|
function slimPost(post) {
|
|
13305
13334
|
const slim = {};
|
|
13306
13335
|
for (const [key, value] of Object.entries(post)) {
|
|
@@ -13381,6 +13410,11 @@ async function handleLinkedinExtract(type, args, ctx) {
|
|
|
13381
13410
|
};
|
|
13382
13411
|
}
|
|
13383
13412
|
case "linkedin_posts": {
|
|
13413
|
+
const profiles = args.profiles;
|
|
13414
|
+
const csvFile = args.csv_file;
|
|
13415
|
+
if (profiles?.length || csvFile) {
|
|
13416
|
+
return handleBatchLinkedinPosts(args, adapter);
|
|
13417
|
+
}
|
|
13384
13418
|
const result = await adapter.getProfilePosts({
|
|
13385
13419
|
profile: args.profile,
|
|
13386
13420
|
postedLimit: args.posted_within
|
|
@@ -13445,12 +13479,234 @@ async function handleLinkedinExtract(type, args, ctx) {
|
|
|
13445
13479
|
throw new Error(`Unknown extract type: ${type}`);
|
|
13446
13480
|
}
|
|
13447
13481
|
}
|
|
13448
|
-
|
|
13482
|
+
async function handleBatchLinkedinPosts(args, adapter) {
|
|
13483
|
+
const profilesArg = args.profiles;
|
|
13484
|
+
const csvFile = args.csv_file;
|
|
13485
|
+
const postedWithin = args.posted_within ?? "month";
|
|
13486
|
+
const concurrency = Math.min(
|
|
13487
|
+
args.concurrency ?? DEFAULT_BATCH_CONCURRENCY,
|
|
13488
|
+
MAX_BATCH_CONCURRENCY
|
|
13489
|
+
);
|
|
13490
|
+
const output = args.output;
|
|
13491
|
+
const shouldReview = args.review ?? false;
|
|
13492
|
+
let profiles;
|
|
13493
|
+
if (csvFile) {
|
|
13494
|
+
profiles = parseCsvProfiles(csvFile);
|
|
13495
|
+
} else {
|
|
13496
|
+
profiles = (profilesArg ?? []).map((url) => ({ linkedin_url: url }));
|
|
13497
|
+
}
|
|
13498
|
+
if (profiles.length === 0) {
|
|
13499
|
+
throw createGtmError("EXTRACTION_FAILED", "No LinkedIn profiles found in input.");
|
|
13500
|
+
}
|
|
13501
|
+
const results = new Array(profiles.length);
|
|
13502
|
+
let nextIndex = 0;
|
|
13503
|
+
let completed = 0;
|
|
13504
|
+
const deadline = Date.now() + BATCH_TIMEOUT_MS;
|
|
13505
|
+
async function worker() {
|
|
13506
|
+
while (nextIndex < profiles.length) {
|
|
13507
|
+
const i = nextIndex++;
|
|
13508
|
+
const entry = profiles[i];
|
|
13509
|
+
if (Date.now() > deadline) {
|
|
13510
|
+
results[i] = {
|
|
13511
|
+
linkedin_url: entry.linkedin_url,
|
|
13512
|
+
name: entry.name,
|
|
13513
|
+
company: entry.company,
|
|
13514
|
+
has_posts: false,
|
|
13515
|
+
post_count: 0,
|
|
13516
|
+
error: "timeout"
|
|
13517
|
+
};
|
|
13518
|
+
completed++;
|
|
13519
|
+
continue;
|
|
13520
|
+
}
|
|
13521
|
+
try {
|
|
13522
|
+
const result = await adapter.getProfilePosts({
|
|
13523
|
+
profile: entry.linkedin_url,
|
|
13524
|
+
postedLimit: postedWithin
|
|
13525
|
+
});
|
|
13526
|
+
const posts = result.elements;
|
|
13527
|
+
let lastPostDate;
|
|
13528
|
+
if (posts.length > 0) {
|
|
13529
|
+
const first = posts[0];
|
|
13530
|
+
lastPostDate = first.postedAt ?? first.postedDate ?? first.posted_at;
|
|
13531
|
+
}
|
|
13532
|
+
results[i] = {
|
|
13533
|
+
linkedin_url: entry.linkedin_url,
|
|
13534
|
+
name: entry.name,
|
|
13535
|
+
company: entry.company,
|
|
13536
|
+
has_posts: posts.length > 0,
|
|
13537
|
+
post_count: posts.length,
|
|
13538
|
+
last_post_date: lastPostDate
|
|
13539
|
+
};
|
|
13540
|
+
} catch (error) {
|
|
13541
|
+
logError("extract:batchLinkedinPosts", error instanceof Error ? error : new Error(String(error)), {
|
|
13542
|
+
profile: entry.linkedin_url
|
|
13543
|
+
});
|
|
13544
|
+
results[i] = {
|
|
13545
|
+
linkedin_url: entry.linkedin_url,
|
|
13546
|
+
name: entry.name,
|
|
13547
|
+
company: entry.company,
|
|
13548
|
+
has_posts: false,
|
|
13549
|
+
post_count: 0,
|
|
13550
|
+
error: error instanceof Error ? error.message : String(error)
|
|
13551
|
+
};
|
|
13552
|
+
}
|
|
13553
|
+
completed++;
|
|
13554
|
+
}
|
|
13555
|
+
}
|
|
13556
|
+
await Promise.all(
|
|
13557
|
+
Array.from({ length: Math.min(concurrency, profiles.length) }, () => worker())
|
|
13558
|
+
);
|
|
13559
|
+
const activeProfiles = results.filter((r) => r.has_posts);
|
|
13560
|
+
const errors = results.filter((r) => r.error);
|
|
13561
|
+
let tableId;
|
|
13562
|
+
let tableUrl;
|
|
13563
|
+
if (shouldReview) {
|
|
13564
|
+
const tableResult = await createReviewTable(
|
|
13565
|
+
`LinkedIn Activity Check (${postedWithin})`,
|
|
13566
|
+
results
|
|
13567
|
+
);
|
|
13568
|
+
if (tableResult) {
|
|
13569
|
+
tableId = tableResult.table_id;
|
|
13570
|
+
tableUrl = tableResult.url;
|
|
13571
|
+
}
|
|
13572
|
+
}
|
|
13573
|
+
const effectiveOutput = output ?? (profiles.length > 20 ? "csv" : "inline");
|
|
13574
|
+
if (effectiveOutput === "csv") {
|
|
13575
|
+
const filePath = writeResultsCsv(
|
|
13576
|
+
"linkedin_activity",
|
|
13577
|
+
results
|
|
13578
|
+
);
|
|
13579
|
+
return {
|
|
13580
|
+
type: "linkedin_posts_batch",
|
|
13581
|
+
provider: "harvest",
|
|
13582
|
+
total: profiles.length,
|
|
13583
|
+
active: activeProfiles.length,
|
|
13584
|
+
inactive: profiles.length - activeProfiles.length - errors.length,
|
|
13585
|
+
errors: errors.length,
|
|
13586
|
+
posted_within: postedWithin,
|
|
13587
|
+
output: "csv",
|
|
13588
|
+
file: filePath,
|
|
13589
|
+
preview: activeProfiles.slice(0, 5),
|
|
13590
|
+
...tableId && { table_id: tableId, url: tableUrl }
|
|
13591
|
+
};
|
|
13592
|
+
}
|
|
13593
|
+
if (effectiveOutput === "file") {
|
|
13594
|
+
const filePath = writeResultsFile(
|
|
13595
|
+
"linkedin_activity",
|
|
13596
|
+
results,
|
|
13597
|
+
{ query: { posted_within: postedWithin }, provider: "harvest", count: results.length, has_more: false }
|
|
13598
|
+
);
|
|
13599
|
+
return {
|
|
13600
|
+
type: "linkedin_posts_batch",
|
|
13601
|
+
provider: "harvest",
|
|
13602
|
+
total: profiles.length,
|
|
13603
|
+
active: activeProfiles.length,
|
|
13604
|
+
inactive: profiles.length - activeProfiles.length - errors.length,
|
|
13605
|
+
errors: errors.length,
|
|
13606
|
+
posted_within: postedWithin,
|
|
13607
|
+
output: "file",
|
|
13608
|
+
file: filePath,
|
|
13609
|
+
preview: activeProfiles.slice(0, 5),
|
|
13610
|
+
...tableId && { table_id: tableId, url: tableUrl }
|
|
13611
|
+
};
|
|
13612
|
+
}
|
|
13613
|
+
return {
|
|
13614
|
+
type: "linkedin_posts_batch",
|
|
13615
|
+
provider: "harvest",
|
|
13616
|
+
total: profiles.length,
|
|
13617
|
+
active: activeProfiles.length,
|
|
13618
|
+
inactive: profiles.length - activeProfiles.length - errors.length,
|
|
13619
|
+
errors: errors.length,
|
|
13620
|
+
posted_within: postedWithin,
|
|
13621
|
+
results,
|
|
13622
|
+
...tableId && { table_id: tableId, url: tableUrl }
|
|
13623
|
+
};
|
|
13624
|
+
}
|
|
13625
|
+
function parseCsvProfiles(filePath) {
|
|
13626
|
+
if (!fs2.existsSync(filePath)) {
|
|
13627
|
+
throw createGtmError("EXTRACTION_FAILED", `CSV file not found: ${filePath}`);
|
|
13628
|
+
}
|
|
13629
|
+
const content = fs2.readFileSync(filePath, "utf-8");
|
|
13630
|
+
const lines = content.split("\n").filter((line) => line.trim().length > 0);
|
|
13631
|
+
if (lines.length < 2) {
|
|
13632
|
+
throw createGtmError("EXTRACTION_FAILED", "CSV file has no data rows.");
|
|
13633
|
+
}
|
|
13634
|
+
const headerLine = lines[0];
|
|
13635
|
+
const headers = parseCsvLine(headerLine).map((h) => h.toLowerCase().trim());
|
|
13636
|
+
const linkedinIdx = headers.findIndex((h) => LINKEDIN_URL_COLUMNS.includes(h));
|
|
13637
|
+
if (linkedinIdx === -1) {
|
|
13638
|
+
throw createGtmError(
|
|
13639
|
+
"EXTRACTION_FAILED",
|
|
13640
|
+
`No LinkedIn URL column found in CSV. Expected one of: ${LINKEDIN_URL_COLUMNS.join(", ")}. Found columns: ${headers.join(", ")}`
|
|
13641
|
+
);
|
|
13642
|
+
}
|
|
13643
|
+
const nameIdx = headers.findIndex((h) => NAME_COLUMNS.includes(h));
|
|
13644
|
+
const companyIdx = headers.findIndex((h) => COMPANY_COLUMNS.includes(h));
|
|
13645
|
+
const entries = [];
|
|
13646
|
+
for (let i = 1; i < lines.length; i++) {
|
|
13647
|
+
const fields = parseCsvLine(lines[i]);
|
|
13648
|
+
const url = fields[linkedinIdx]?.trim();
|
|
13649
|
+
if (!url || !url.includes("linkedin.com")) continue;
|
|
13650
|
+
entries.push({
|
|
13651
|
+
linkedin_url: url,
|
|
13652
|
+
name: nameIdx >= 0 ? fields[nameIdx]?.trim() : void 0,
|
|
13653
|
+
company: companyIdx >= 0 ? fields[companyIdx]?.trim() : void 0
|
|
13654
|
+
});
|
|
13655
|
+
}
|
|
13656
|
+
return entries;
|
|
13657
|
+
}
|
|
13658
|
+
function parseCsvLine(line) {
|
|
13659
|
+
const fields = [];
|
|
13660
|
+
let current = "";
|
|
13661
|
+
let inQuotes = false;
|
|
13662
|
+
for (let i = 0; i < line.length; i++) {
|
|
13663
|
+
const char = line[i];
|
|
13664
|
+
if (inQuotes) {
|
|
13665
|
+
if (char === '"') {
|
|
13666
|
+
if (i + 1 < line.length && line[i + 1] === '"') {
|
|
13667
|
+
current += '"';
|
|
13668
|
+
i++;
|
|
13669
|
+
} else {
|
|
13670
|
+
inQuotes = false;
|
|
13671
|
+
}
|
|
13672
|
+
} else {
|
|
13673
|
+
current += char;
|
|
13674
|
+
}
|
|
13675
|
+
} else if (char === '"') {
|
|
13676
|
+
inQuotes = true;
|
|
13677
|
+
} else if (char === ",") {
|
|
13678
|
+
fields.push(current);
|
|
13679
|
+
current = "";
|
|
13680
|
+
} else {
|
|
13681
|
+
current += char;
|
|
13682
|
+
}
|
|
13683
|
+
}
|
|
13684
|
+
fields.push(current);
|
|
13685
|
+
return fields;
|
|
13686
|
+
}
|
|
13687
|
+
var POST_TEXT_LIMIT, DEFAULT_BATCH_CONCURRENCY, MAX_BATCH_CONCURRENCY, BATCH_TIMEOUT_MS, LINKEDIN_URL_COLUMNS, NAME_COLUMNS, COMPANY_COLUMNS;
|
|
13449
13688
|
var init_extract2 = __esm({
|
|
13450
13689
|
"src/handlers/extract.ts"() {
|
|
13451
13690
|
"use strict";
|
|
13691
|
+
init_dist();
|
|
13452
13692
|
init_errors();
|
|
13693
|
+
init_table_bridge();
|
|
13694
|
+
init_result_writer();
|
|
13453
13695
|
POST_TEXT_LIMIT = 200;
|
|
13696
|
+
DEFAULT_BATCH_CONCURRENCY = 5;
|
|
13697
|
+
MAX_BATCH_CONCURRENCY = 20;
|
|
13698
|
+
BATCH_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
13699
|
+
LINKEDIN_URL_COLUMNS = [
|
|
13700
|
+
"linkedin profile",
|
|
13701
|
+
"linkedin_profile",
|
|
13702
|
+
"linkedin url",
|
|
13703
|
+
"linkedin_url",
|
|
13704
|
+
"linkedin",
|
|
13705
|
+
"profile url",
|
|
13706
|
+
"profile_url"
|
|
13707
|
+
];
|
|
13708
|
+
NAME_COLUMNS = ["full name", "full_name", "name"];
|
|
13709
|
+
COMPANY_COLUMNS = ["company name", "company_name", "company"];
|
|
13454
13710
|
}
|
|
13455
13711
|
});
|
|
13456
13712
|
|
|
@@ -13857,7 +14113,9 @@ async function handleCrm(args, ctx) {
|
|
|
13857
14113
|
if (rows.length === 0) {
|
|
13858
14114
|
return {
|
|
13859
14115
|
created: 0,
|
|
13860
|
-
hints: [
|
|
14116
|
+
hints: [
|
|
14117
|
+
"No selected rows found in the review table. Select rows in the viewer and retry."
|
|
14118
|
+
]
|
|
13861
14119
|
};
|
|
13862
14120
|
}
|
|
13863
14121
|
const contacts = mapRowsToContacts(rows);
|
|
@@ -13887,7 +14145,10 @@ async function handleCrm(args, ctx) {
|
|
|
13887
14145
|
case "find_company": {
|
|
13888
14146
|
const query = args.query;
|
|
13889
14147
|
const name = args.name ?? query?.name;
|
|
13890
|
-
if (!name)
|
|
14148
|
+
if (!name)
|
|
14149
|
+
throw new Error(
|
|
14150
|
+
'name is required for find_company action (pass as name="X" or query={"name":"X"})'
|
|
14151
|
+
);
|
|
13891
14152
|
if (!manager.findCompany) throw new Error("Provider does not support company lookup");
|
|
13892
14153
|
return manager.findCompany(name);
|
|
13893
14154
|
}
|
|
@@ -14089,53 +14350,49 @@ __export(feedback_exports, {
|
|
|
14089
14350
|
handleFeedback: () => handleFeedback
|
|
14090
14351
|
});
|
|
14091
14352
|
async function handleFeedback(args) {
|
|
14092
|
-
const
|
|
14093
|
-
|
|
14094
|
-
|
|
14095
|
-
|
|
14096
|
-
|
|
14097
|
-
|
|
14098
|
-
|
|
14099
|
-
|
|
14100
|
-
|
|
14101
|
-
|
|
14102
|
-
|
|
14103
|
-
|
|
14104
|
-
|
|
14105
|
-
|
|
14106
|
-
|
|
14107
|
-
|
|
14108
|
-
|
|
14109
|
-
|
|
14110
|
-
|
|
14111
|
-
|
|
14112
|
-
|
|
14113
|
-
|
|
14114
|
-
|
|
14115
|
-
|
|
14353
|
+
const type = args.type;
|
|
14354
|
+
const message = args.message.trim();
|
|
14355
|
+
const context = args.context ?? {};
|
|
14356
|
+
try {
|
|
14357
|
+
const response = await fetch(`${VIEWER_URL2}/api/feedback`, {
|
|
14358
|
+
method: "POST",
|
|
14359
|
+
headers: { "Content-Type": "application/json" },
|
|
14360
|
+
body: JSON.stringify({ type, message, context })
|
|
14361
|
+
});
|
|
14362
|
+
if (!response.ok) {
|
|
14363
|
+
const body = await response.text().catch(() => "unknown");
|
|
14364
|
+
throw createGtmError(
|
|
14365
|
+
"PROVIDER_REQUEST_FAILED",
|
|
14366
|
+
`Failed to submit feedback: ${response.status}`,
|
|
14367
|
+
{
|
|
14368
|
+
details: { status: response.status, body }
|
|
14369
|
+
}
|
|
14370
|
+
);
|
|
14371
|
+
}
|
|
14372
|
+
const data = await response.json();
|
|
14373
|
+
const created = data[0];
|
|
14374
|
+
return {
|
|
14375
|
+
submitted: true,
|
|
14376
|
+
id: created?.id,
|
|
14377
|
+
type,
|
|
14378
|
+
message: `Feedback submitted. Thank you! (ID: ${created?.id ?? "unknown"})`
|
|
14379
|
+
};
|
|
14380
|
+
} catch (error) {
|
|
14381
|
+
if (error && typeof error === "object" && "error_code" in error) throw error;
|
|
14382
|
+
logError("feedback:submit", error instanceof Error ? error : new Error(String(error)));
|
|
14116
14383
|
throw createGtmError(
|
|
14117
14384
|
"PROVIDER_REQUEST_FAILED",
|
|
14118
|
-
|
|
14119
|
-
{
|
|
14120
|
-
details: { status: response.status, body }
|
|
14121
|
-
}
|
|
14385
|
+
"Failed to submit feedback. Please try again later."
|
|
14122
14386
|
);
|
|
14123
14387
|
}
|
|
14124
|
-
const [created] = await response.json();
|
|
14125
|
-
return {
|
|
14126
|
-
submitted: true,
|
|
14127
|
-
id: created?.id,
|
|
14128
|
-
type: input.type,
|
|
14129
|
-
message: `Feedback submitted. Thank you! (ID: ${created?.id ?? "unknown"})`
|
|
14130
|
-
};
|
|
14131
14388
|
}
|
|
14132
|
-
var
|
|
14389
|
+
var VIEWER_URL2;
|
|
14133
14390
|
var init_feedback2 = __esm({
|
|
14134
14391
|
"src/handlers/feedback.ts"() {
|
|
14135
14392
|
"use strict";
|
|
14393
|
+
init_dist();
|
|
14136
14394
|
init_errors();
|
|
14137
|
-
|
|
14138
|
-
SUPABASE_KEY = process.env.GTM_SUPABASE_KEY ?? process.env.SUPABASE_SERVICE_ROLE_KEY ?? "";
|
|
14395
|
+
VIEWER_URL2 = process.env.GTM_VIEWER_URL ?? "https://gtm-viewer-production.up.railway.app";
|
|
14139
14396
|
}
|
|
14140
14397
|
});
|
|
14141
14398
|
|
|
@@ -14166,7 +14423,7 @@ async function createTable(args) {
|
|
|
14166
14423
|
...row,
|
|
14167
14424
|
_row_id: row._row_id ?? `r${i + 1}`
|
|
14168
14425
|
}));
|
|
14169
|
-
const response = await fetch(`${
|
|
14426
|
+
const response = await fetch(`${VIEWER_URL3}/api/tables`, {
|
|
14170
14427
|
method: "POST",
|
|
14171
14428
|
headers: { "Content-Type": "application/json" },
|
|
14172
14429
|
body: JSON.stringify({
|
|
@@ -14186,7 +14443,7 @@ async function createTable(args) {
|
|
|
14186
14443
|
async function updateTable(args) {
|
|
14187
14444
|
const tableId = args.table_id;
|
|
14188
14445
|
const rows = args.rows;
|
|
14189
|
-
const response = await fetch(`${
|
|
14446
|
+
const response = await fetch(`${VIEWER_URL3}/api/tables/${tableId}`, {
|
|
14190
14447
|
method: "PATCH",
|
|
14191
14448
|
headers: { "Content-Type": "application/json" },
|
|
14192
14449
|
body: JSON.stringify({ rows })
|
|
@@ -14199,18 +14456,18 @@ async function updateTable(args) {
|
|
|
14199
14456
|
}
|
|
14200
14457
|
async function getSelected(args) {
|
|
14201
14458
|
const tableId = args.table_id;
|
|
14202
|
-
const response = await fetch(`${
|
|
14459
|
+
const response = await fetch(`${VIEWER_URL3}/api/tables/${tableId}/selected`);
|
|
14203
14460
|
if (!response.ok) {
|
|
14204
14461
|
const body = await response.json().catch(() => ({}));
|
|
14205
14462
|
throw new Error(`Viewer API error ${response.status}: ${JSON.stringify(body)}`);
|
|
14206
14463
|
}
|
|
14207
14464
|
return response.json();
|
|
14208
14465
|
}
|
|
14209
|
-
var
|
|
14466
|
+
var VIEWER_URL3;
|
|
14210
14467
|
var init_review2 = __esm({
|
|
14211
14468
|
"src/handlers/review.ts"() {
|
|
14212
14469
|
"use strict";
|
|
14213
|
-
|
|
14470
|
+
VIEWER_URL3 = process.env.GTM_VIEWER_URL ?? "https://gtm-viewer-production.up.railway.app";
|
|
14214
14471
|
}
|
|
14215
14472
|
});
|
|
14216
14473
|
|
|
@@ -14539,6 +14796,9 @@ var init_validation = __esm({
|
|
|
14539
14796
|
linkedin_url: z2.string().optional(),
|
|
14540
14797
|
find_email: z2.boolean().optional(),
|
|
14541
14798
|
profile: z2.string().optional(),
|
|
14799
|
+
profiles: z2.array(z2.string()).optional(),
|
|
14800
|
+
csv_file: z2.string().optional(),
|
|
14801
|
+
concurrency: z2.number().int().min(1).max(20).optional(),
|
|
14542
14802
|
company: z2.string().optional(),
|
|
14543
14803
|
posted_within: z2.enum(["24h", "week", "month"]).optional(),
|
|
14544
14804
|
post_url: z2.string().optional(),
|
|
@@ -14547,14 +14807,16 @@ var init_validation = __esm({
|
|
|
14547
14807
|
sort: z2.enum(["relevance", "date"]).optional(),
|
|
14548
14808
|
limit: z2.number().int().min(1).max(100).optional(),
|
|
14549
14809
|
fields: z2.array(z2.string()).optional(),
|
|
14550
|
-
detail: detailSchema
|
|
14810
|
+
detail: detailSchema,
|
|
14811
|
+
output: z2.enum(["file", "csv", "inline"]).optional(),
|
|
14812
|
+
review: z2.boolean().optional()
|
|
14551
14813
|
}).refine(
|
|
14552
14814
|
(data) => {
|
|
14553
14815
|
switch (data.type) {
|
|
14554
14816
|
case "linkedin_profile":
|
|
14555
14817
|
return !!(data.linkedin_url || data.url);
|
|
14556
14818
|
case "linkedin_posts":
|
|
14557
|
-
return !!data.profile;
|
|
14819
|
+
return !!(data.profile || data.profiles?.length || data.csv_file);
|
|
14558
14820
|
case "linkedin_engagement":
|
|
14559
14821
|
return !!data.post_url;
|
|
14560
14822
|
case "linkedin_search":
|
|
@@ -14815,7 +15077,7 @@ var init_server = __esm({
|
|
|
14815
15077
|
});
|
|
14816
15078
|
|
|
14817
15079
|
// src/cli-commands.ts
|
|
14818
|
-
import { readFileSync as
|
|
15080
|
+
import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
|
|
14819
15081
|
function registerCommands(program, exec2) {
|
|
14820
15082
|
buildServeCommand(program);
|
|
14821
15083
|
buildStatusCommand(program, exec2);
|
|
@@ -14944,11 +15206,11 @@ function parseContactsArg(value, writeError2) {
|
|
|
14944
15206
|
writeError2("Failed to parse --contacts as JSON", { received: trimmed.slice(0, 200) });
|
|
14945
15207
|
}
|
|
14946
15208
|
}
|
|
14947
|
-
if (!
|
|
15209
|
+
if (!existsSync3(value)) {
|
|
14948
15210
|
writeError2(`Contacts file not found: ${value}`);
|
|
14949
15211
|
}
|
|
14950
15212
|
try {
|
|
14951
|
-
const content =
|
|
15213
|
+
const content = readFileSync3(value, "utf-8");
|
|
14952
15214
|
const parsed = JSON.parse(content);
|
|
14953
15215
|
return Array.isArray(parsed) ? parsed : [parsed];
|
|
14954
15216
|
} catch (err) {
|