salesprompter-cli 0.1.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.
- package/.codex/environments/environment.toml +19 -0
- package/README.md +54 -0
- package/data/deel-icp.json +17 -0
- package/data/deel-leads.json +77 -0
- package/data/enriched.json +106 -0
- package/data/icp.json +24 -0
- package/data/leads.json +62 -0
- package/data/scored.json +142 -0
- package/dist/cli.js +114 -0
- package/dist/domain.js +36 -0
- package/dist/engine.js +147 -0
- package/dist/io.js +17 -0
- package/dist/providers.js +1 -0
- package/dist/sample-data.js +34 -0
- package/dist-tests/src/cli.js +114 -0
- package/dist-tests/src/domain.js +36 -0
- package/dist-tests/src/engine.js +147 -0
- package/dist-tests/src/io.js +17 -0
- package/dist-tests/src/providers.js +1 -0
- package/dist-tests/src/sample-data.js +34 -0
- package/dist-tests/tests/cli.test.js +149 -0
- package/package.json +31 -0
- package/src/cli.ts +136 -0
- package/src/domain.ts +50 -0
- package/src/engine.ts +170 -0
- package/src/io.ts +21 -0
- package/src/providers.ts +22 -0
- package/src/sample-data.ts +37 -0
- package/tests/cli.test.ts +184 -0
- package/tsconfig.json +16 -0
- package/tsconfig.test.json +8 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { execFile } from "node:child_process";
|
|
7
|
+
import { promisify } from "node:util";
|
|
8
|
+
|
|
9
|
+
const execFileAsync = promisify(execFile);
|
|
10
|
+
const projectRoot = path.resolve(import.meta.dirname, "..", "..");
|
|
11
|
+
const cliPath = path.join(projectRoot, "dist", "cli.js");
|
|
12
|
+
|
|
13
|
+
async function runCli(args: string[]): Promise<unknown> {
|
|
14
|
+
const { stdout } = await execFileAsync("node", [cliPath, ...args], {
|
|
15
|
+
cwd: projectRoot
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
return JSON.parse(stdout);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
test("CLI help renders the expected command surface", async () => {
|
|
22
|
+
const { stdout } = await execFileAsync("node", [cliPath, "--help"], {
|
|
23
|
+
cwd: projectRoot
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
assert.match(stdout, /icp:define/);
|
|
27
|
+
assert.match(stdout, /leads:generate/);
|
|
28
|
+
assert.match(stdout, /sync:outreach/);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("CLI workflow generates, enriches, scores, and syncs leads", async () => {
|
|
32
|
+
const tempDir = await mkdtemp(path.join(os.tmpdir(), "salesprompter-cli-"));
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const icpPath = path.join(tempDir, "icp.json");
|
|
36
|
+
const leadsPath = path.join(tempDir, "leads.json");
|
|
37
|
+
const enrichedPath = path.join(tempDir, "enriched.json");
|
|
38
|
+
const scoredPath = path.join(tempDir, "scored.json");
|
|
39
|
+
|
|
40
|
+
const icpResult = await runCli([
|
|
41
|
+
"icp:define",
|
|
42
|
+
"--name",
|
|
43
|
+
"EU SaaS RevOps",
|
|
44
|
+
"--industries",
|
|
45
|
+
"Software,Financial Services",
|
|
46
|
+
"--company-sizes",
|
|
47
|
+
"50-199,200-499",
|
|
48
|
+
"--regions",
|
|
49
|
+
"Europe",
|
|
50
|
+
"--titles",
|
|
51
|
+
"Head of Revenue Operations,VP Sales",
|
|
52
|
+
"--required-signals",
|
|
53
|
+
"recent funding,growing outbound team",
|
|
54
|
+
"--out",
|
|
55
|
+
icpPath
|
|
56
|
+
]);
|
|
57
|
+
|
|
58
|
+
assert.equal((icpResult as { status: string }).status, "ok");
|
|
59
|
+
|
|
60
|
+
const generateResult = await runCli([
|
|
61
|
+
"leads:generate",
|
|
62
|
+
"--icp",
|
|
63
|
+
icpPath,
|
|
64
|
+
"--count",
|
|
65
|
+
"4",
|
|
66
|
+
"--out",
|
|
67
|
+
leadsPath
|
|
68
|
+
]);
|
|
69
|
+
|
|
70
|
+
assert.equal((generateResult as { generated: number }).generated, 4);
|
|
71
|
+
|
|
72
|
+
const enrichResult = await runCli([
|
|
73
|
+
"leads:enrich",
|
|
74
|
+
"--in",
|
|
75
|
+
leadsPath,
|
|
76
|
+
"--out",
|
|
77
|
+
enrichedPath
|
|
78
|
+
]);
|
|
79
|
+
|
|
80
|
+
assert.equal((enrichResult as { enriched: number }).enriched, 4);
|
|
81
|
+
|
|
82
|
+
const scoreResult = await runCli([
|
|
83
|
+
"leads:score",
|
|
84
|
+
"--icp",
|
|
85
|
+
icpPath,
|
|
86
|
+
"--in",
|
|
87
|
+
enrichedPath,
|
|
88
|
+
"--out",
|
|
89
|
+
scoredPath
|
|
90
|
+
]);
|
|
91
|
+
|
|
92
|
+
assert.equal((scoreResult as { scored: number }).scored, 4);
|
|
93
|
+
|
|
94
|
+
const crmSyncResult = await runCli([
|
|
95
|
+
"sync:crm",
|
|
96
|
+
"--target",
|
|
97
|
+
"hubspot",
|
|
98
|
+
"--in",
|
|
99
|
+
scoredPath
|
|
100
|
+
]);
|
|
101
|
+
|
|
102
|
+
assert.deepEqual(crmSyncResult, {
|
|
103
|
+
status: "ok",
|
|
104
|
+
target: "hubspot",
|
|
105
|
+
synced: 4,
|
|
106
|
+
dryRun: true
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const scoredLeads = JSON.parse(await readFile(scoredPath, "utf8")) as Array<{
|
|
110
|
+
score: number;
|
|
111
|
+
grade: string;
|
|
112
|
+
rationale: string[];
|
|
113
|
+
crmFit: string;
|
|
114
|
+
outreachFit: string;
|
|
115
|
+
}>;
|
|
116
|
+
|
|
117
|
+
assert.equal(scoredLeads.length, 4);
|
|
118
|
+
assert.ok(scoredLeads.every((lead) => lead.score >= 0 && lead.score <= 100));
|
|
119
|
+
assert.ok(scoredLeads.every((lead) => ["A", "B", "C", "D"].includes(lead.grade)));
|
|
120
|
+
assert.ok(scoredLeads.some((lead) => lead.rationale.length > 0));
|
|
121
|
+
assert.ok(scoredLeads.some((lead) => lead.crmFit === "high" || lead.outreachFit === "high"));
|
|
122
|
+
} finally {
|
|
123
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("CLI can target a specific company domain like deel.com", async () => {
|
|
128
|
+
const tempDir = await mkdtemp(path.join(os.tmpdir(), "salesprompter-cli-target-"));
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
const icpPath = path.join(tempDir, "icp.json");
|
|
132
|
+
const leadsPath = path.join(tempDir, "deel-leads.json");
|
|
133
|
+
|
|
134
|
+
await runCli([
|
|
135
|
+
"icp:define",
|
|
136
|
+
"--name",
|
|
137
|
+
"Global HR Tech",
|
|
138
|
+
"--industries",
|
|
139
|
+
"Software",
|
|
140
|
+
"--regions",
|
|
141
|
+
"Global",
|
|
142
|
+
"--titles",
|
|
143
|
+
"VP Sales,Head of Revenue Operations",
|
|
144
|
+
"--out",
|
|
145
|
+
icpPath
|
|
146
|
+
]);
|
|
147
|
+
|
|
148
|
+
const generateResult = await runCli([
|
|
149
|
+
"leads:generate",
|
|
150
|
+
"--icp",
|
|
151
|
+
icpPath,
|
|
152
|
+
"--count",
|
|
153
|
+
"3",
|
|
154
|
+
"--company-domain",
|
|
155
|
+
"deel.com",
|
|
156
|
+
"--company-name",
|
|
157
|
+
"Deel",
|
|
158
|
+
"--out",
|
|
159
|
+
leadsPath
|
|
160
|
+
]);
|
|
161
|
+
|
|
162
|
+
assert.deepEqual(generateResult, {
|
|
163
|
+
status: "ok",
|
|
164
|
+
generated: 3,
|
|
165
|
+
out: leadsPath,
|
|
166
|
+
target: "deel.com"
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const leads = JSON.parse(await readFile(leadsPath, "utf8")) as Array<{
|
|
170
|
+
companyName: string;
|
|
171
|
+
domain: string;
|
|
172
|
+
email: string;
|
|
173
|
+
source: string;
|
|
174
|
+
}>;
|
|
175
|
+
|
|
176
|
+
assert.equal(leads.length, 3);
|
|
177
|
+
assert.ok(leads.every((lead) => lead.companyName === "Deel"));
|
|
178
|
+
assert.ok(leads.every((lead) => lead.domain === "deel.com"));
|
|
179
|
+
assert.ok(leads.every((lead) => lead.email.endsWith("@deel.com")));
|
|
180
|
+
assert.ok(leads.every((lead) => lead.source === "heuristic-target-account"));
|
|
181
|
+
} finally {
|
|
182
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
183
|
+
}
|
|
184
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"lib": ["ES2022"],
|
|
7
|
+
"strict": true,
|
|
8
|
+
"rootDir": "src",
|
|
9
|
+
"outDir": "dist",
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"forceConsistentCasingInFileNames": true
|
|
14
|
+
},
|
|
15
|
+
"include": ["src/**/*.ts"]
|
|
16
|
+
}
|