heyreach-cli 0.1.1 → 0.1.3

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 CHANGED
@@ -64,7 +64,9 @@ var init_config = __esm({
64
64
  // src/core/errors.ts
65
65
  function classifyHttpError(status, body) {
66
66
  const parsed = safeParse(body);
67
- const message = parsed?.message ?? parsed?.error ?? body;
67
+ const rawVal = parsed?.message || parsed?.error || body || `HTTP ${status} error`;
68
+ const raw = typeof rawVal === "string" ? rawVal : JSON.stringify(rawVal);
69
+ const message = extractMessage(raw);
68
70
  if (status === 401 || status === 403) return new AuthError(message);
69
71
  if (status === 404) return new NotFoundError(message);
70
72
  if (status === 422) return new ValidationError(message);
@@ -72,6 +74,18 @@ function classifyHttpError(status, body) {
72
74
  if (status >= 500) return new ServerError(message);
73
75
  return new HeyReachError(message, "HTTP_ERROR", status);
74
76
  }
77
+ function extractMessage(raw) {
78
+ const nested = safeParse(raw);
79
+ if (nested?.errors && typeof nested.errors === "object") {
80
+ const parts = [];
81
+ for (const [field, msgs] of Object.entries(nested.errors)) {
82
+ const msgList = Array.isArray(msgs) ? msgs.join("; ") : String(msgs);
83
+ parts.push(`${field}: ${msgList}`);
84
+ }
85
+ if (parts.length > 0) return parts.join(". ");
86
+ }
87
+ return raw;
88
+ }
75
89
  function safeParse(text) {
76
90
  try {
77
91
  return JSON.parse(text);
@@ -255,7 +269,9 @@ function createClient(auth) {
255
269
  return { request, paginate };
256
270
  }
257
271
  function buildUrl(baseUrl, path2, query) {
258
- const url = new URL(path2, baseUrl.endsWith("/") ? baseUrl : baseUrl + "/");
272
+ const base = baseUrl.endsWith("/") ? baseUrl : baseUrl + "/";
273
+ const cleanPath = path2.startsWith("/") ? path2.slice(1) : path2;
274
+ const url = new URL(cleanPath, base);
259
275
  if (query) {
260
276
  for (const [key, value] of Object.entries(query)) {
261
277
  if (value !== void 0 && value !== null) {
@@ -631,6 +647,7 @@ var init_get_for_lead = __esm({
631
647
  "use strict";
632
648
  init_esm_shims();
633
649
  init_handler();
650
+ init_errors();
634
651
  campaignsGetForLeadCommand = {
635
652
  name: "campaigns_get_for_lead",
636
653
  group: "campaigns",
@@ -658,7 +675,12 @@ var init_get_for_lead = __esm({
658
675
  },
659
676
  endpoint: { method: "POST", path: "/campaign/GetCampaignsForLead" },
660
677
  fieldMappings: { offset: "body", limit: "body", email: "body", linkedinId: "body", profileUrl: "body" },
661
- handler: (input, client) => executeCommand(campaignsGetForLeadCommand, input, client)
678
+ handler: (input, client) => {
679
+ if (!input.email && !input.linkedinId && !input.profileUrl) {
680
+ throw new ValidationError("At least one of --email, --linkedin-id, or --profile-url is required.");
681
+ }
682
+ return executeCommand(campaignsGetForLeadCommand, input, client);
683
+ }
662
684
  };
663
685
  }
664
686
  });
@@ -1257,6 +1279,7 @@ var init_get_for_lead2 = __esm({
1257
1279
  "use strict";
1258
1280
  init_esm_shims();
1259
1281
  init_handler();
1282
+ init_errors();
1260
1283
  listsGetForLeadCommand = {
1261
1284
  name: "lists_get_for_lead",
1262
1285
  group: "lists",
@@ -1281,7 +1304,12 @@ var init_get_for_lead2 = __esm({
1281
1304
  },
1282
1305
  endpoint: { method: "POST", path: "/list/GetListsForLead" },
1283
1306
  fieldMappings: { offset: "body", limit: "body", email: "body", linkedinId: "body", profileUrl: "body" },
1284
- handler: (input, client) => executeCommand(listsGetForLeadCommand, input, client)
1307
+ handler: (input, client) => {
1308
+ if (!input.email && !input.linkedinId && !input.profileUrl) {
1309
+ throw new ValidationError("At least one of --email, --linkedin-id, or --profile-url is required.");
1310
+ }
1311
+ return executeCommand(listsGetForLeadCommand, input, client);
1312
+ }
1285
1313
  };
1286
1314
  }
1287
1315
  });
@@ -1332,8 +1360,8 @@ var init_overview = __esm({
1332
1360
  'heyreach stats overview --start-date 2025-01-01 --end-date 2025-01-31 --campaign-ids "1,2,3"'
1333
1361
  ],
1334
1362
  inputSchema: z24.object({
1335
- startDate: z24.string().describe("Start date (ISO 8601)"),
1336
- endDate: z24.string().describe("End date (ISO 8601)"),
1363
+ startDate: z24.string().optional().describe("Start date (ISO 8601). Defaults to 30 days ago."),
1364
+ endDate: z24.string().optional().describe("End date (ISO 8601). Defaults to today."),
1337
1365
  accountIds: z24.string().optional().describe("Comma-separated LinkedIn account IDs (empty = all)"),
1338
1366
  campaignIds: z24.string().optional().describe("Comma-separated campaign IDs (empty = all)")
1339
1367
  }),
@@ -1348,12 +1376,14 @@ var init_overview = __esm({
1348
1376
  endpoint: { method: "POST", path: "/stats/GetOverallStats" },
1349
1377
  fieldMappings: {},
1350
1378
  handler: async (input, client) => {
1379
+ const now = /* @__PURE__ */ new Date();
1380
+ const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1e3);
1351
1381
  const body = {
1352
- startDate: input.startDate,
1353
- endDate: input.endDate
1382
+ startDate: input.startDate ?? thirtyDaysAgo.toISOString(),
1383
+ endDate: input.endDate ?? now.toISOString(),
1384
+ accountIds: input.accountIds ? input.accountIds.split(",").map((s) => Number(s.trim())) : [],
1385
+ campaignIds: input.campaignIds ? input.campaignIds.split(",").map((s) => Number(s.trim())) : []
1354
1386
  };
1355
- if (input.accountIds) body.accountIds = input.accountIds.split(",").map((s) => Number(s.trim()));
1356
- if (input.campaignIds) body.campaignIds = input.campaignIds.split(",").map((s) => Number(s.trim()));
1357
1387
  return client.request({ method: "POST", path: "/stats/GetOverallStats", body });
1358
1388
  }
1359
1389
  };
@@ -2404,7 +2434,7 @@ function getGlobalOpts(program2) {
2404
2434
  };
2405
2435
  }
2406
2436
  function registerLoginCommand(program2) {
2407
- program2.command("login").description("Save your API key to ~/.heyreach/config.json").option("--org", "Store Organization API key instead of workspace key").action(async (opts) => {
2437
+ program2.command("login").description("Save your API key to ~/.heyreach/config.json").option("--org", "Store Organization API key instead of workspace key").addHelpText("after", "\nExamples:\n $ heyreach login --api-key <key>\n $ heyreach login --org --org-key <key>\n $ heyreach login # interactive prompt").action(async (opts) => {
2408
2438
  const globalOpts = getGlobalOpts(program2);
2409
2439
  const isOrg = opts.org;
2410
2440
  let apiKey = isOrg ? globalOpts.orgKey ?? process.env.HEYREACH_ORG_API_KEY : globalOpts.apiKey ?? process.env.HEYREACH_API_KEY;
@@ -2418,9 +2448,17 @@ function registerLoginCommand(program2) {
2418
2448
  outputError({ error: "API key is required.", code: "VALIDATION_ERROR" }, globalOpts);
2419
2449
  return;
2420
2450
  }
2451
+ try {
2452
+ const checkPath = isOrg ? "/organization/GetWorkspaces" : "/auth/CheckApiKey";
2453
+ const client = createClient({ apiKey, baseUrl: "https://api.heyreach.io/api/public" });
2454
+ await client.request({ method: "GET", path: checkPath });
2455
+ } catch {
2456
+ outputError({ error: `Invalid ${isOrg ? "Organization" : ""} API key. Check your key and try again.`, code: "AUTH_ERROR" }, globalOpts);
2457
+ return;
2458
+ }
2421
2459
  const config = isOrg ? { org_api_key: apiKey } : { api_key: apiKey };
2422
2460
  saveConfig(config);
2423
- output({ success: true, message: "Credentials saved.", config_path: getConfigPath() }, globalOpts);
2461
+ output({ success: true, message: "Credentials saved and verified.", config_path: getConfigPath() }, globalOpts);
2424
2462
  });
2425
2463
  }
2426
2464
  function registerLogoutCommand(program2) {
@@ -2444,9 +2482,11 @@ function registerStatusCommand(program2) {
2444
2482
  }, globalOpts);
2445
2483
  } catch (err) {
2446
2484
  const config = loadConfig();
2485
+ const usedKey = globalOpts.apiKey ?? process.env.HEYREACH_API_KEY ?? config.api_key;
2447
2486
  output({
2448
2487
  authenticated: false,
2449
- api_key: config.api_key ? "***" + config.api_key.slice(-4) : "(not set)",
2488
+ api_key: usedKey ? "***" + usedKey.slice(-4) : "(not set)",
2489
+ api_key_source: globalOpts.apiKey ? "--api-key flag" : process.env.HEYREACH_API_KEY ? "HEYREACH_API_KEY env" : config.api_key ? "config file" : "none",
2450
2490
  config_path: getConfigPath(),
2451
2491
  error: err instanceof Error ? err.message : String(err)
2452
2492
  }, globalOpts);
@@ -2606,7 +2646,7 @@ import { Command } from "commander";
2606
2646
  var program = new Command();
2607
2647
  program.name("heyreach").description(
2608
2648
  "HeyReach CLI \u2014 manage LinkedIn campaigns, leads, lists, inbox, webhooks, and more from your terminal."
2609
- ).version("0.1.0").option("--pretty", "Pretty-print JSON output").option("--quiet", "Suppress output, exit codes only").option("--fields <fields>", "Comma-separated fields to include in output").option("--api-key <key>", "HeyReach workspace API key").option("--org-key <key>", "HeyReach Organization API key");
2649
+ ).version("0.1.3").option("--pretty", "Pretty-print JSON output").option("--quiet", "Suppress output, exit codes only").option("--fields <fields>", "Comma-separated fields to include in output").option("--api-key <key>", "HeyReach workspace API key").option("--org-key <key>", "HeyReach Organization API key");
2610
2650
  registerAllCommands(program);
2611
2651
  program.parse();
2612
2652
  //# sourceMappingURL=index.js.map