@lhremote/cli 0.5.0 → 0.7.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 (79) hide show
  1. package/README.md +5 -4
  2. package/dist/handlers/add-people-to-collection.d.ts +10 -0
  3. package/dist/handlers/add-people-to-collection.d.ts.map +1 -0
  4. package/dist/handlers/add-people-to-collection.js +79 -0
  5. package/dist/handlers/add-people-to-collection.js.map +1 -0
  6. package/dist/handlers/add-people-to-collection.test.d.ts +2 -0
  7. package/dist/handlers/add-people-to-collection.test.d.ts.map +1 -0
  8. package/dist/handlers/add-people-to-collection.test.js +107 -0
  9. package/dist/handlers/add-people-to-collection.test.js.map +1 -0
  10. package/dist/handlers/build-url.d.ts +17 -0
  11. package/dist/handlers/build-url.d.ts.map +1 -0
  12. package/dist/handlers/build-url.js +90 -0
  13. package/dist/handlers/build-url.js.map +1 -0
  14. package/dist/handlers/collect-people.d.ts +12 -0
  15. package/dist/handlers/collect-people.d.ts.map +1 -0
  16. package/dist/handlers/collect-people.js +39 -0
  17. package/dist/handlers/collect-people.js.map +1 -0
  18. package/dist/handlers/collect-people.test.d.ts +2 -0
  19. package/dist/handlers/collect-people.test.d.ts.map +1 -0
  20. package/dist/handlers/collect-people.test.js +92 -0
  21. package/dist/handlers/collect-people.test.js.map +1 -0
  22. package/dist/handlers/create-collection.d.ts +8 -0
  23. package/dist/handlers/create-collection.d.ts.map +1 -0
  24. package/dist/handlers/create-collection.js +26 -0
  25. package/dist/handlers/create-collection.js.map +1 -0
  26. package/dist/handlers/create-collection.test.d.ts +2 -0
  27. package/dist/handlers/create-collection.test.d.ts.map +1 -0
  28. package/dist/handlers/create-collection.test.js +55 -0
  29. package/dist/handlers/create-collection.test.js.map +1 -0
  30. package/dist/handlers/delete-collection.d.ts +8 -0
  31. package/dist/handlers/delete-collection.d.ts.map +1 -0
  32. package/dist/handlers/delete-collection.js +31 -0
  33. package/dist/handlers/delete-collection.js.map +1 -0
  34. package/dist/handlers/delete-collection.test.d.ts +2 -0
  35. package/dist/handlers/delete-collection.test.d.ts.map +1 -0
  36. package/dist/handlers/delete-collection.test.js +74 -0
  37. package/dist/handlers/delete-collection.test.js.map +1 -0
  38. package/dist/handlers/import-people-from-collection.d.ts +8 -0
  39. package/dist/handlers/import-people-from-collection.d.ts.map +1 -0
  40. package/dist/handlers/import-people-from-collection.js +55 -0
  41. package/dist/handlers/import-people-from-collection.js.map +1 -0
  42. package/dist/handlers/import-people-from-collection.test.d.ts +2 -0
  43. package/dist/handlers/import-people-from-collection.test.d.ts.map +1 -0
  44. package/dist/handlers/import-people-from-collection.test.js +121 -0
  45. package/dist/handlers/import-people-from-collection.test.js.map +1 -0
  46. package/dist/handlers/index.d.ts +10 -0
  47. package/dist/handlers/index.d.ts.map +1 -1
  48. package/dist/handlers/index.js +10 -0
  49. package/dist/handlers/index.js.map +1 -1
  50. package/dist/handlers/list-collections.d.ts +5 -0
  51. package/dist/handlers/list-collections.d.ts.map +1 -0
  52. package/dist/handlers/list-collections.js +48 -0
  53. package/dist/handlers/list-collections.js.map +1 -0
  54. package/dist/handlers/list-collections.test.d.ts +2 -0
  55. package/dist/handlers/list-collections.test.d.ts.map +1 -0
  56. package/dist/handlers/list-collections.test.js +111 -0
  57. package/dist/handlers/list-collections.test.js.map +1 -0
  58. package/dist/handlers/list-reference-data.d.ts +5 -0
  59. package/dist/handlers/list-reference-data.d.ts.map +1 -0
  60. package/dist/handlers/list-reference-data.js +33 -0
  61. package/dist/handlers/list-reference-data.js.map +1 -0
  62. package/dist/handlers/remove-people-from-collection.d.ts +10 -0
  63. package/dist/handlers/remove-people-from-collection.d.ts.map +1 -0
  64. package/dist/handlers/remove-people-from-collection.js +75 -0
  65. package/dist/handlers/remove-people-from-collection.js.map +1 -0
  66. package/dist/handlers/remove-people-from-collection.test.d.ts +2 -0
  67. package/dist/handlers/remove-people-from-collection.test.d.ts.map +1 -0
  68. package/dist/handlers/remove-people-from-collection.test.js +100 -0
  69. package/dist/handlers/remove-people-from-collection.test.js.map +1 -0
  70. package/dist/handlers/resolve-entity.d.ts +9 -0
  71. package/dist/handlers/resolve-entity.d.ts.map +1 -0
  72. package/dist/handlers/resolve-entity.js +48 -0
  73. package/dist/handlers/resolve-entity.js.map +1 -0
  74. package/dist/program.d.ts.map +1 -1
  75. package/dist/program.js +105 -1
  76. package/dist/program.js.map +1 -1
  77. package/dist/program.test.js +11 -1
  78. package/dist/program.test.js.map +1 -1
  79. package/package.json +2 -2
@@ -0,0 +1,111 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // Copyright (C) 2026 Oleksii PELYKH
3
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
4
+ vi.mock("@lhremote/core", async (importOriginal) => {
5
+ const actual = await importOriginal();
6
+ return {
7
+ ...actual,
8
+ DatabaseClient: vi.fn(),
9
+ CollectionListRepository: vi.fn(),
10
+ discoverAllDatabases: vi.fn(),
11
+ };
12
+ });
13
+ import { CollectionListRepository } from "@lhremote/core";
14
+ import { handleListCollections } from "./list-collections.js";
15
+ import { getStdout, mockDb, mockDiscovery } from "./testing/mock-helpers.js";
16
+ const MOCK_COLLECTIONS = [
17
+ {
18
+ id: 1,
19
+ name: "Prospects Q1",
20
+ peopleCount: 42,
21
+ createdAt: "2025-01-01T00:00:00Z",
22
+ },
23
+ {
24
+ id: 2,
25
+ name: "Follow-Up List",
26
+ peopleCount: 7,
27
+ createdAt: "2025-01-02T00:00:00Z",
28
+ },
29
+ ];
30
+ function mockRepo(collections = MOCK_COLLECTIONS) {
31
+ vi.mocked(CollectionListRepository).mockImplementation(function () {
32
+ return {
33
+ listCollections: vi.fn().mockReturnValue(collections),
34
+ };
35
+ });
36
+ }
37
+ function setupSuccessPath() {
38
+ mockDiscovery();
39
+ mockDb();
40
+ mockRepo();
41
+ }
42
+ describe("handleListCollections", () => {
43
+ const originalExitCode = process.exitCode;
44
+ let stdoutSpy;
45
+ let stderrSpy;
46
+ beforeEach(() => {
47
+ process.exitCode = undefined;
48
+ vi.clearAllMocks();
49
+ stdoutSpy = vi.spyOn(process.stdout, "write").mockReturnValue(true);
50
+ stderrSpy = vi.spyOn(process.stderr, "write").mockReturnValue(true);
51
+ });
52
+ afterEach(() => {
53
+ process.exitCode = originalExitCode;
54
+ vi.restoreAllMocks();
55
+ });
56
+ it("prints JSON with --json", async () => {
57
+ setupSuccessPath();
58
+ await handleListCollections({ json: true });
59
+ expect(process.exitCode).toBeUndefined();
60
+ const parsed = JSON.parse(getStdout(stdoutSpy));
61
+ expect(parsed.collections).toHaveLength(2);
62
+ expect(parsed.total).toBe(2);
63
+ });
64
+ it("prints human-readable output", async () => {
65
+ setupSuccessPath();
66
+ await handleListCollections({});
67
+ expect(process.exitCode).toBeUndefined();
68
+ const output = getStdout(stdoutSpy);
69
+ expect(output).toContain("Collections (2 total):");
70
+ expect(output).toContain("#1 Prospects Q1");
71
+ expect(output).toContain("42 people");
72
+ expect(output).toContain("#2 Follow-Up List");
73
+ expect(output).toContain("7 people");
74
+ });
75
+ it("prints 'No collections found' when empty", async () => {
76
+ mockDiscovery();
77
+ mockDb();
78
+ mockRepo([]);
79
+ await handleListCollections({});
80
+ expect(process.exitCode).toBeUndefined();
81
+ expect(getStdout(stdoutSpy)).toContain("No collections found.");
82
+ });
83
+ it("sets exitCode 1 when no databases found", async () => {
84
+ mockDiscovery(new Map());
85
+ await handleListCollections({});
86
+ expect(process.exitCode).toBe(1);
87
+ expect(stderrSpy).toHaveBeenCalledWith("No LinkedHelper databases found.\n");
88
+ });
89
+ it("closes database after listing", async () => {
90
+ mockDiscovery();
91
+ const { close } = mockDb();
92
+ mockRepo();
93
+ await handleListCollections({});
94
+ expect(close).toHaveBeenCalledOnce();
95
+ });
96
+ it("sets exitCode 1 on database error", async () => {
97
+ mockDiscovery();
98
+ mockDb();
99
+ vi.mocked(CollectionListRepository).mockImplementation(function () {
100
+ return {
101
+ listCollections: vi.fn().mockImplementation(() => {
102
+ throw new Error("database locked");
103
+ }),
104
+ };
105
+ });
106
+ await handleListCollections({});
107
+ expect(process.exitCode).toBe(1);
108
+ expect(stderrSpy).toHaveBeenCalledWith(expect.stringContaining("database locked"));
109
+ });
110
+ });
111
+ //# sourceMappingURL=list-collections.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list-collections.test.js","sourceRoot":"","sources":["../../src/handlers/list-collections.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /** Handle the {@link https://github.com/alexey-pelykh/lhremote#list-reference-data | list-reference-data} CLI command. */
2
+ export declare function handleListReferenceData(dataType: string, options: {
3
+ json?: boolean;
4
+ }): void;
5
+ //# sourceMappingURL=list-reference-data.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list-reference-data.d.ts","sourceRoot":"","sources":["../../src/handlers/list-reference-data.ts"],"names":[],"mappings":"AAkBA,0HAA0H;AAC1H,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE;IACP,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,GACA,IAAI,CA6BN"}
@@ -0,0 +1,33 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // Copyright (C) 2026 Oleksii PELYKH
3
+ import { getLinkedInReferenceData, isReferenceDataType, } from "@lhremote/core";
4
+ const VALID_DATA_TYPES = [
5
+ "INDUSTRY",
6
+ "SENIORITY",
7
+ "FUNCTION",
8
+ "COMPANY_SIZE",
9
+ "CONNECTION_DEGREE",
10
+ "PROFILE_LANGUAGE",
11
+ ];
12
+ /** Handle the {@link https://github.com/alexey-pelykh/lhremote#list-reference-data | list-reference-data} CLI command. */
13
+ export function handleListReferenceData(dataType, options) {
14
+ if (!isReferenceDataType(dataType)) {
15
+ process.stderr.write(`Unknown reference data type: ${dataType}\n` +
16
+ `Valid types: ${VALID_DATA_TYPES.join(", ")}\n`);
17
+ process.exitCode = 1;
18
+ return;
19
+ }
20
+ const items = getLinkedInReferenceData(dataType);
21
+ if (options.json) {
22
+ process.stdout.write(JSON.stringify({ dataType, items }, null, 2) + "\n");
23
+ return;
24
+ }
25
+ process.stdout.write(`${dataType} (${String(items.length)} entries):\n\n`);
26
+ for (const item of items) {
27
+ // Each entry type has different key names; normalise for display
28
+ const entries = Object.entries(item);
29
+ const parts = entries.map(([key, value]) => `${key}: ${String(value)}`);
30
+ process.stdout.write(` ${parts.join(", ")}\n`);
31
+ }
32
+ }
33
+ //# sourceMappingURL=list-reference-data.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list-reference-data.js","sourceRoot":"","sources":["../../src/handlers/list-reference-data.ts"],"names":[],"mappings":"AAAA,yCAAyC;AACzC,oCAAoC;AAEpC,OAAO,EAEL,wBAAwB,EACxB,mBAAmB,GACpB,MAAM,gBAAgB,CAAC;AAExB,MAAM,gBAAgB,GAAiC;IACrD,UAAU;IACV,WAAW;IACX,UAAU;IACV,cAAc;IACd,mBAAmB;IACnB,kBAAkB;CACnB,CAAC;AAEF,0HAA0H;AAC1H,MAAM,UAAU,uBAAuB,CACrC,QAAgB,EAChB,OAEC;IAED,IAAI,CAAC,mBAAmB,CAAC,QAAQ,CAAC,EAAE,CAAC;QACnC,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,gCAAgC,QAAQ,IAAI;YAC1C,gBAAgB,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAClD,CAAC;QACF,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACrB,OAAO;IACT,CAAC;IAED,MAAM,KAAK,GAAG,wBAAwB,CAAC,QAAQ,CAAC,CAAC;IAEjD,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;QACjB,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CACpD,CAAC;QACF,OAAO;IACT,CAAC;IAED,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,QAAQ,KAAK,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;IAE3E,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,iEAAiE;QACjE,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,IAA0C,CAAC,CAAC;QAC3E,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CACvB,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,KAAK,MAAM,CAAC,KAAK,CAAC,EAAE,CAC7C,CAAC;QACF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAClD,CAAC;AACH,CAAC"}
@@ -0,0 +1,10 @@
1
+ /** Handle the {@link https://github.com/alexey-pelykh/lhremote#remove-people-from-collection | remove-people-from-collection} CLI command. */
2
+ export declare function handleRemovePeopleFromCollection(collectionId: number, options: {
3
+ personIds?: string;
4
+ personIdsFile?: string;
5
+ cdpPort?: number;
6
+ cdpHost?: string;
7
+ allowRemote?: boolean;
8
+ json?: boolean;
9
+ }): Promise<void>;
10
+ //# sourceMappingURL=remove-people-from-collection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remove-people-from-collection.d.ts","sourceRoot":"","sources":["../../src/handlers/remove-people-from-collection.ts"],"names":[],"mappings":"AA8BA,8IAA8I;AAC9I,wBAAsB,gCAAgC,CACpD,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE;IACP,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,GACA,OAAO,CAAC,IAAI,CAAC,CAoDf"}
@@ -0,0 +1,75 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // Copyright (C) 2026 Oleksii PELYKH
3
+ import { readFileSync } from "node:fs";
4
+ import { DEFAULT_CDP_PORT, removePeopleFromCollection, errorMessage, } from "@lhremote/core";
5
+ function parsePersonIds(raw) {
6
+ return raw
7
+ .split(",")
8
+ .map((s) => s.trim())
9
+ .filter((s) => s.length > 0)
10
+ .map(Number)
11
+ .filter((n) => Number.isInteger(n) && n > 0);
12
+ }
13
+ function readPersonIdsFile(filePath) {
14
+ const content = readFileSync(filePath, "utf-8");
15
+ return content
16
+ .split(/[\n,]/)
17
+ .map((s) => s.trim())
18
+ .filter((s) => s.length > 0)
19
+ .map(Number)
20
+ .filter((n) => Number.isInteger(n) && n > 0);
21
+ }
22
+ /** Handle the {@link https://github.com/alexey-pelykh/lhremote#remove-people-from-collection | remove-people-from-collection} CLI command. */
23
+ export async function handleRemovePeopleFromCollection(collectionId, options) {
24
+ if (options.personIds && options.personIdsFile) {
25
+ process.stderr.write("Use only one of --person-ids or --person-ids-file.\n");
26
+ process.exitCode = 1;
27
+ return;
28
+ }
29
+ let personIds;
30
+ if (options.personIds) {
31
+ personIds = parsePersonIds(options.personIds);
32
+ }
33
+ else if (options.personIdsFile) {
34
+ try {
35
+ personIds = readPersonIdsFile(options.personIdsFile);
36
+ }
37
+ catch (error) {
38
+ const message = errorMessage(error);
39
+ process.stderr.write(`${message}\n`);
40
+ process.exitCode = 1;
41
+ return;
42
+ }
43
+ }
44
+ else {
45
+ process.stderr.write("Either --person-ids or --person-ids-file is required.\n");
46
+ process.exitCode = 1;
47
+ return;
48
+ }
49
+ if (personIds.length === 0) {
50
+ process.stderr.write("No valid person IDs provided.\n");
51
+ process.exitCode = 1;
52
+ return;
53
+ }
54
+ try {
55
+ const result = await removePeopleFromCollection({
56
+ collectionId,
57
+ personIds,
58
+ cdpPort: options.cdpPort ?? DEFAULT_CDP_PORT,
59
+ cdpHost: options.cdpHost,
60
+ allowRemote: options.allowRemote,
61
+ });
62
+ if (options.json) {
63
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
64
+ }
65
+ else {
66
+ process.stdout.write(`Removed ${String(result.removed)} people from collection #${String(collectionId)}.\n`);
67
+ }
68
+ }
69
+ catch (error) {
70
+ const message = errorMessage(error);
71
+ process.stderr.write(`${message}\n`);
72
+ process.exitCode = 1;
73
+ }
74
+ }
75
+ //# sourceMappingURL=remove-people-from-collection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remove-people-from-collection.js","sourceRoot":"","sources":["../../src/handlers/remove-people-from-collection.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=remove-people-from-collection.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remove-people-from-collection.test.d.ts","sourceRoot":"","sources":["../../src/handlers/remove-people-from-collection.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,100 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // Copyright (C) 2026 Oleksii PELYKH
3
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
4
+ vi.mock("@lhremote/core", async (importOriginal) => {
5
+ const actual = await importOriginal();
6
+ return {
7
+ ...actual,
8
+ removePeopleFromCollection: vi.fn(),
9
+ };
10
+ });
11
+ vi.mock("node:fs", async (importOriginal) => {
12
+ const actual = await importOriginal();
13
+ return {
14
+ ...actual,
15
+ readFileSync: vi.fn(),
16
+ };
17
+ });
18
+ import { removePeopleFromCollection, } from "@lhremote/core";
19
+ import { readFileSync } from "node:fs";
20
+ import { handleRemovePeopleFromCollection } from "./remove-people-from-collection.js";
21
+ import { getStdout } from "./testing/mock-helpers.js";
22
+ const MOCK_RESULT = {
23
+ success: true,
24
+ collectionId: 1,
25
+ removed: 2,
26
+ };
27
+ describe("handleRemovePeopleFromCollection", () => {
28
+ const originalExitCode = process.exitCode;
29
+ let stdoutSpy;
30
+ let stderrSpy;
31
+ beforeEach(() => {
32
+ process.exitCode = undefined;
33
+ vi.clearAllMocks();
34
+ stdoutSpy = vi.spyOn(process.stdout, "write").mockReturnValue(true);
35
+ stderrSpy = vi.spyOn(process.stderr, "write").mockReturnValue(true);
36
+ });
37
+ afterEach(() => {
38
+ process.exitCode = originalExitCode;
39
+ vi.restoreAllMocks();
40
+ });
41
+ it("removes people with --person-ids and prints result", async () => {
42
+ vi.mocked(removePeopleFromCollection).mockResolvedValue(MOCK_RESULT);
43
+ await handleRemovePeopleFromCollection(1, { personIds: "100,200" });
44
+ expect(process.exitCode).toBeUndefined();
45
+ expect(getStdout(stdoutSpy)).toContain("Removed 2 people from collection #1.");
46
+ });
47
+ it("reads from --person-ids-file", async () => {
48
+ vi.mocked(readFileSync).mockReturnValue("100\n200\n300");
49
+ vi.mocked(removePeopleFromCollection).mockResolvedValue({
50
+ ...MOCK_RESULT,
51
+ removed: 3,
52
+ });
53
+ await handleRemovePeopleFromCollection(1, { personIdsFile: "ids.txt" });
54
+ expect(process.exitCode).toBeUndefined();
55
+ expect(getStdout(stdoutSpy)).toContain("Removed 3 people");
56
+ });
57
+ it("prints JSON with --json", async () => {
58
+ vi.mocked(removePeopleFromCollection).mockResolvedValue(MOCK_RESULT);
59
+ await handleRemovePeopleFromCollection(1, { personIds: "100,200", json: true });
60
+ expect(process.exitCode).toBeUndefined();
61
+ const parsed = JSON.parse(getStdout(stdoutSpy));
62
+ expect(parsed.success).toBe(true);
63
+ expect(parsed.collectionId).toBe(1);
64
+ expect(parsed.removed).toBe(2);
65
+ });
66
+ it("sets exitCode 1 when both person-ids options provided", async () => {
67
+ await handleRemovePeopleFromCollection(1, {
68
+ personIds: "100",
69
+ personIdsFile: "ids.txt",
70
+ });
71
+ expect(process.exitCode).toBe(1);
72
+ expect(stderrSpy).toHaveBeenCalledWith("Use only one of --person-ids or --person-ids-file.\n");
73
+ });
74
+ it("sets exitCode 1 when no person-ids option provided", async () => {
75
+ await handleRemovePeopleFromCollection(1, {});
76
+ expect(process.exitCode).toBe(1);
77
+ expect(stderrSpy).toHaveBeenCalledWith("Either --person-ids or --person-ids-file is required.\n");
78
+ });
79
+ it("sets exitCode 1 when person IDs are empty", async () => {
80
+ vi.mocked(readFileSync).mockReturnValue("");
81
+ await handleRemovePeopleFromCollection(1, { personIdsFile: "empty.txt" });
82
+ expect(process.exitCode).toBe(1);
83
+ expect(stderrSpy).toHaveBeenCalledWith("No valid person IDs provided.\n");
84
+ });
85
+ it("sets exitCode 1 on error", async () => {
86
+ vi.mocked(removePeopleFromCollection).mockRejectedValue(new Error("timeout"));
87
+ await handleRemovePeopleFromCollection(1, { personIds: "100" });
88
+ expect(process.exitCode).toBe(1);
89
+ expect(stderrSpy).toHaveBeenCalledWith("timeout\n");
90
+ });
91
+ it("sets exitCode 1 when person-ids-file read fails", async () => {
92
+ vi.mocked(readFileSync).mockImplementation(() => {
93
+ throw new Error("ENOENT: no such file");
94
+ });
95
+ await handleRemovePeopleFromCollection(1, { personIdsFile: "missing.txt" });
96
+ expect(process.exitCode).toBe(1);
97
+ expect(stderrSpy).toHaveBeenCalledWith(expect.stringContaining("ENOENT"));
98
+ });
99
+ });
100
+ //# sourceMappingURL=remove-people-from-collection.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remove-people-from-collection.test.js","sourceRoot":"","sources":["../../src/handlers/remove-people-from-collection.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /** Handle the {@link https://github.com/alexey-pelykh/lhremote#resolve-entity | resolve-entity} CLI command. */
2
+ export declare function handleResolveEntity(entityType: string, query: string, options: {
3
+ cdpPort?: number;
4
+ cdpHost?: string;
5
+ allowRemote?: boolean;
6
+ json?: boolean;
7
+ limit?: number;
8
+ }): Promise<void>;
9
+ //# sourceMappingURL=resolve-entity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-entity.d.ts","sourceRoot":"","sources":["../../src/handlers/resolve-entity.ts"],"names":[],"mappings":"AAgBA,gHAAgH;AAChH,wBAAsB,mBAAmB,CACvC,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE;IACP,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GACA,OAAO,CAAC,IAAI,CAAC,CA+Cf"}
@@ -0,0 +1,48 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // Copyright (C) 2026 Oleksii PELYKH
3
+ import { DEFAULT_CDP_PORT, resolveLinkedInEntity, errorMessage, } from "@lhremote/core";
4
+ const VALID_ENTITY_TYPES = [
5
+ "COMPANY",
6
+ "GEO",
7
+ "SCHOOL",
8
+ ];
9
+ /** Handle the {@link https://github.com/alexey-pelykh/lhremote#resolve-entity | resolve-entity} CLI command. */
10
+ export async function handleResolveEntity(entityType, query, options) {
11
+ if (!VALID_ENTITY_TYPES.includes(entityType)) {
12
+ process.stderr.write(`Unknown entity type: ${entityType}\n` +
13
+ `Valid types: ${VALID_ENTITY_TYPES.join(", ")}\n`);
14
+ process.exitCode = 1;
15
+ return;
16
+ }
17
+ try {
18
+ const result = await resolveLinkedInEntity({
19
+ query,
20
+ entityType: entityType,
21
+ cdpPort: options.cdpPort ?? DEFAULT_CDP_PORT,
22
+ ...(options.cdpHost !== undefined && { cdpHost: options.cdpHost }),
23
+ ...(options.allowRemote !== undefined && { allowRemote: options.allowRemote }),
24
+ });
25
+ let matches = result.matches;
26
+ if (options.limit !== undefined) {
27
+ matches = matches.slice(0, options.limit);
28
+ }
29
+ if (options.json) {
30
+ process.stdout.write(JSON.stringify({ matches, strategy: result.strategy }, null, 2) + "\n");
31
+ return;
32
+ }
33
+ if (matches.length === 0) {
34
+ process.stdout.write(`No matches found for "${query}"\n`);
35
+ return;
36
+ }
37
+ process.stdout.write(`Matches for "${query}" (${entityType}, via ${result.strategy}):\n\n`);
38
+ for (const match of matches) {
39
+ process.stdout.write(` ${match.id} ${match.name} [${match.type}]\n`);
40
+ }
41
+ }
42
+ catch (error) {
43
+ const message = errorMessage(error);
44
+ process.stderr.write(`${message}\n`);
45
+ process.exitCode = 1;
46
+ }
47
+ }
48
+ //# sourceMappingURL=resolve-entity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-entity.js","sourceRoot":"","sources":["../../src/handlers/resolve-entity.ts"],"names":[],"mappings":"AAAA,yCAAyC;AACzC,oCAAoC;AAEpC,OAAO,EACL,gBAAgB,EAEhB,qBAAqB,EACrB,YAAY,GACb,MAAM,gBAAgB,CAAC;AAExB,MAAM,kBAAkB,GAA0B;IAChD,SAAS;IACT,KAAK;IACL,QAAQ;CACT,CAAC;AAEF,gHAAgH;AAChH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,UAAkB,EAClB,KAAa,EACb,OAMC;IAED,IAAI,CAAC,kBAAkB,CAAC,QAAQ,CAAC,UAAwB,CAAC,EAAE,CAAC;QAC3D,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,wBAAwB,UAAU,IAAI;YACpC,gBAAgB,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CACpD,CAAC;QACF,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACrB,OAAO;IACT,CAAC;IAED,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,qBAAqB,CAAC;YACzC,KAAK;YACL,UAAU,EAAE,UAAwB;YACpC,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,gBAAgB;YAC5C,GAAG,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC;YAClE,GAAG,CAAC,OAAO,CAAC,WAAW,KAAK,SAAS,IAAI,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC;SAC/E,CAAC,CAAC;QAEH,IAAI,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC;QAC7B,IAAI,OAAO,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;YAChC,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;QAC5C,CAAC;QAED,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;YACjB,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CACvE,CAAC;YACF,OAAO;QACT,CAAC;QAED,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,yBAAyB,KAAK,KAAK,CAAC,CAAC;YAC1D,OAAO;QACT,CAAC;QAED,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,gBAAgB,KAAK,MAAM,UAAU,SAAS,MAAM,CAAC,QAAQ,QAAQ,CACtE,CAAC;QACF,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,KAAK,CAAC,EAAE,KAAK,KAAK,CAAC,IAAI,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC;QAC1E,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,OAAO,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;QACpC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,OAAO,IAAI,CAAC,CAAC;QACrC,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;IACvB,CAAC;AACH,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"program.d.ts","sourceRoot":"","sources":["../src/program.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,OAAO,EAAgC,MAAM,WAAW,CAAC;AAsFlE;;GAEG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAwbvC"}
1
+ {"version":3,"file":"program.d.ts","sourceRoot":"","sources":["../src/program.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,OAAO,EAAgC,MAAM,WAAW,CAAC;AAgGlE;;GAEG;AACH,wBAAgB,aAAa,IAAI,OAAO,CA0iBvC"}
package/dist/program.js CHANGED
@@ -2,7 +2,7 @@
2
2
  // Copyright (C) 2026 Oleksii PELYKH
3
3
  import { createRequire } from "node:module";
4
4
  import { Command, InvalidArgumentError, Option } from "commander";
5
- import { handleCampaignAddAction, handleCampaignCreate, handleCampaignDelete, handleCampaignExcludeAdd, handleCampaignExcludeList, handleCampaignExcludeRemove, handleCampaignExport, handleCampaignGet, handleCampaignList, handleCampaignListPeople, handleCampaignMoveNext, handleCampaignRemoveAction, handleCampaignRemovePeople, handleCampaignReorderActions, handleCampaignRetry, handleCampaignStart, handleCampaignStatistics, handleCampaignStatus, handleCampaignStop, handleCampaignUpdate, handleCampaignUpdateAction, handleImportPeopleFromUrls, handleCheckReplies, handleCheckStatus, handleDescribeActions, handleFindApp, handleGetErrors, handleLaunchApp, handleListAccounts, handleQueryMessages, handleQueryProfile, handleQueryProfiles, handleQueryProfilesBulk, handleScrapeMessagingHistory, handleQuitApp, handleStartInstance, handleStopInstance, } from "./handlers/index.js";
5
+ import { handleAddPeopleToCollection, handleBuildUrl, handleCampaignAddAction, handleCampaignCreate, handleCampaignDelete, handleCampaignExcludeAdd, handleCampaignExcludeList, handleCampaignExcludeRemove, handleCampaignExport, handleCampaignGet, handleCampaignList, handleCampaignListPeople, handleCampaignMoveNext, handleCampaignRemoveAction, handleCampaignRemovePeople, handleCampaignReorderActions, handleCampaignRetry, handleCampaignStart, handleCampaignStatistics, handleCampaignStatus, handleCampaignStop, handleCampaignUpdate, handleCampaignUpdateAction, handleCreateCollection, handleDeleteCollection, handleImportPeopleFromCollection, handleImportPeopleFromUrls, handleListCollections, handleCheckReplies, handleCheckStatus, handleCollectPeople, handleDescribeActions, handleFindApp, handleGetErrors, handleLaunchApp, handleListAccounts, handleListReferenceData, handleQueryMessages, handleQueryProfile, handleQueryProfiles, handleQueryProfilesBulk, handleRemovePeopleFromCollection, handleResolveEntity, handleScrapeMessagingHistory, handleQuitApp, handleStartInstance, handleStopInstance, } from "./handlers/index.js";
6
6
  const require = createRequire(import.meta.url);
7
7
  const { version } = require("../package.json");
8
8
  /** Parse a string as a positive integer, throwing on invalid input. */
@@ -315,6 +315,20 @@ export function createProgram() {
315
315
  .option("--allow-remote", "SECURITY: allow non-loopback CDP connections (enables remote code execution on target)")
316
316
  .option("--json", "Output as JSON")
317
317
  .action(handleImportPeopleFromUrls);
318
+ program
319
+ .command("collect-people")
320
+ .description("Collect people from a LinkedIn page into a campaign")
321
+ .argument("<campaignId>", "Campaign ID to collect into", parsePositiveInt)
322
+ .argument("<sourceUrl>", "LinkedIn page URL to collect from")
323
+ .option("--limit <n>", "Max profiles to collect", parsePositiveInt)
324
+ .option("--max-pages <n>", "Max pages to process", parsePositiveInt)
325
+ .option("--page-size <n>", "Results per page", parsePositiveInt)
326
+ .option("--source-type <type>", "Explicit source type (bypasses URL detection)")
327
+ .option("--cdp-port <port>", "CDP debugging port", parsePositiveInt)
328
+ .option("--cdp-host <host>", "CDP host (default: 127.0.0.1)")
329
+ .option("--allow-remote", "SECURITY: allow non-loopback CDP connections (enables remote code execution on target)")
330
+ .option("--json", "Output as JSON")
331
+ .action(handleCollectPeople);
318
332
  program
319
333
  .command("campaign-remove-people")
320
334
  .description("Remove people from a campaign's target list entirely")
@@ -326,6 +340,61 @@ export function createProgram() {
326
340
  .option("--allow-remote", "SECURITY: allow non-loopback CDP connections (enables remote code execution on target)")
327
341
  .option("--json", "Output as JSON")
328
342
  .action(handleCampaignRemovePeople);
343
+ program
344
+ .command("list-collections")
345
+ .description("List LinkedHelper collections (Lists)")
346
+ .option("--json", "Output as JSON")
347
+ .action(handleListCollections);
348
+ program
349
+ .command("create-collection")
350
+ .description("Create a new LinkedHelper collection (List)")
351
+ .argument("<name>", "Name for the new collection")
352
+ .option("--cdp-port <port>", "CDP debugging port", parsePositiveInt)
353
+ .option("--cdp-host <host>", "CDP host (default: 127.0.0.1)")
354
+ .option("--allow-remote", "SECURITY: allow non-loopback CDP connections (enables remote code execution on target)")
355
+ .option("--json", "Output as JSON")
356
+ .action(handleCreateCollection);
357
+ program
358
+ .command("delete-collection")
359
+ .description("Delete a LinkedHelper collection (List) and its people associations")
360
+ .argument("<collectionId>", "Collection ID to delete", parsePositiveInt)
361
+ .option("--cdp-port <port>", "CDP debugging port", parsePositiveInt)
362
+ .option("--cdp-host <host>", "CDP host (default: 127.0.0.1)")
363
+ .option("--allow-remote", "SECURITY: allow non-loopback CDP connections (enables remote code execution on target)")
364
+ .option("--json", "Output as JSON")
365
+ .action(handleDeleteCollection);
366
+ program
367
+ .command("add-people-to-collection")
368
+ .description("Add people to a LinkedHelper collection (List)")
369
+ .argument("<collectionId>", "Collection ID", parsePositiveInt)
370
+ .option("--person-ids <ids>", "Comma-separated person IDs")
371
+ .option("--person-ids-file <path>", "File containing person IDs")
372
+ .option("--cdp-port <port>", "CDP debugging port", parsePositiveInt)
373
+ .option("--cdp-host <host>", "CDP host (default: 127.0.0.1)")
374
+ .option("--allow-remote", "SECURITY: allow non-loopback CDP connections (enables remote code execution on target)")
375
+ .option("--json", "Output as JSON")
376
+ .action(handleAddPeopleToCollection);
377
+ program
378
+ .command("remove-people-from-collection")
379
+ .description("Remove people from a LinkedHelper collection (List)")
380
+ .argument("<collectionId>", "Collection ID", parsePositiveInt)
381
+ .option("--person-ids <ids>", "Comma-separated person IDs")
382
+ .option("--person-ids-file <path>", "File containing person IDs")
383
+ .option("--cdp-port <port>", "CDP debugging port", parsePositiveInt)
384
+ .option("--cdp-host <host>", "CDP host (default: 127.0.0.1)")
385
+ .option("--allow-remote", "SECURITY: allow non-loopback CDP connections (enables remote code execution on target)")
386
+ .option("--json", "Output as JSON")
387
+ .action(handleRemovePeopleFromCollection);
388
+ program
389
+ .command("import-people-from-collection")
390
+ .description("Import people from a LinkedHelper collection (List) into a campaign")
391
+ .argument("<collectionId>", "Collection ID to import from", parsePositiveInt)
392
+ .argument("<campaignId>", "Campaign ID to import into", parsePositiveInt)
393
+ .option("--cdp-port <port>", "CDP debugging port", parsePositiveInt)
394
+ .option("--cdp-host <host>", "CDP host (default: 127.0.0.1)")
395
+ .option("--allow-remote", "SECURITY: allow non-loopback CDP connections (enables remote code execution on target)")
396
+ .option("--json", "Output as JSON")
397
+ .action(handleImportPeopleFromCollection);
329
398
  program
330
399
  .command("describe-actions")
331
400
  .description("List available LinkedHelper action types")
@@ -403,6 +472,41 @@ export function createProgram() {
403
472
  .option("--allow-remote", "SECURITY: allow non-loopback CDP connections (enables remote code execution on target)")
404
473
  .option("--json", "Output as JSON")
405
474
  .action(handleGetErrors);
475
+ program
476
+ .command("build-url")
477
+ .description("Build a LinkedIn URL for a given source type")
478
+ .argument("<sourceType>", "Source type (e.g., SearchPage, SNSearchPage, OrganizationPeople)")
479
+ .option("--keywords <keywords>", "Search keywords (SearchPage, SNSearchPage)")
480
+ .option("--current-company <id>", "Current company ID (SearchPage, repeatable)", collectString, [])
481
+ .option("--past-company <id>", "Past company ID (SearchPage, repeatable)", collectString, [])
482
+ .option("--geo <id>", "Geographic URN ID (SearchPage, repeatable)", collectString, [])
483
+ .option("--industry <id>", "Industry ID (SearchPage, repeatable)", collectString, [])
484
+ .option("--school <id>", "School ID (SearchPage, repeatable)", collectString, [])
485
+ .option("--network <code>", "Connection degree: F, S, O (SearchPage, repeatable)", collectString, [])
486
+ .option("--profile-language <code>", "Profile language code (SearchPage, repeatable)", collectString, [])
487
+ .option("--service-category <id>", "Service category ID (SearchPage, repeatable)", collectString, [])
488
+ .option("--filter <spec>", "SN filter TYPE|ID|TEXT|INCLUDED (SNSearchPage, repeatable)", collectString, [])
489
+ .option("--slug <slug>", "Company or school slug (OrganizationPeople, Alumni)")
490
+ .option("--id <id>", "Entity ID (Group, Event, SNListPage, etc.)")
491
+ .option("--json", "Output as JSON")
492
+ .action(handleBuildUrl);
493
+ program
494
+ .command("resolve-entity")
495
+ .description("Resolve a LinkedIn entity (company, geo, school) by name")
496
+ .argument("<entityType>", "Entity type: COMPANY, GEO, or SCHOOL")
497
+ .argument("<query>", "Search query")
498
+ .option("--limit <n>", "Max results to show", parsePositiveInt)
499
+ .option("--cdp-port <port>", "CDP debugging port", parsePositiveInt)
500
+ .option("--cdp-host <host>", "CDP host (default: 127.0.0.1)")
501
+ .option("--allow-remote", "SECURITY: allow non-loopback CDP connections (enables remote code execution on target)")
502
+ .option("--json", "Output as JSON")
503
+ .action(handleResolveEntity);
504
+ program
505
+ .command("list-reference-data")
506
+ .description("List LinkedIn reference data (industries, seniorities, functions, etc.)")
507
+ .argument("<dataType>", "Data type: INDUSTRY, SENIORITY, FUNCTION, COMPANY_SIZE, CONNECTION_DEGREE, PROFILE_LANGUAGE")
508
+ .option("--json", "Output as JSON")
509
+ .action(handleListReferenceData);
406
510
  return program;
407
511
  }
408
512
  //# sourceMappingURL=program.js.map