kingminos 0.0.0-stage → 0.2.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.
@@ -0,0 +1,1823 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/kingminos.ts
4
+ import { readFileSync, realpathSync } from "fs";
5
+ import { fileURLToPath } from "url";
6
+
7
+ // src/sanitize.ts
8
+ function stripSafeToWrite(value) {
9
+ if (Array.isArray(value)) {
10
+ return value.map(stripSafeToWrite);
11
+ }
12
+ if (value !== null && typeof value === "object") {
13
+ const out = {};
14
+ for (const [key, child] of Object.entries(value)) {
15
+ if (key === "safe_to_write") {
16
+ continue;
17
+ }
18
+ out[key] = stripSafeToWrite(child);
19
+ }
20
+ return out;
21
+ }
22
+ return value;
23
+ }
24
+
25
+ // src/client.ts
26
+ async function apiRequest(input) {
27
+ const headers = new Headers(input.headers);
28
+ headers.set("x-kingminos-client", "cli");
29
+ if (input.token) {
30
+ headers.set("Authorization", `Bearer ${input.token}`);
31
+ }
32
+ let body;
33
+ if (input.body !== void 0) {
34
+ headers.set("content-type", "application/json");
35
+ body = JSON.stringify(input.body);
36
+ }
37
+ const response = await input.fetch(input.url, {
38
+ method: input.method,
39
+ headers,
40
+ body
41
+ });
42
+ return {
43
+ status: response.status,
44
+ contentType: response.headers.get("content-type") ?? "",
45
+ text: await response.text()
46
+ };
47
+ }
48
+ function formatBody(text, contentType) {
49
+ const trimmed = text.replace(/^\uFEFF/, "");
50
+ if (trimmed === "") {
51
+ return "";
52
+ }
53
+ const jsonLike = contentType.includes("json") || looksLikeJson(trimmed);
54
+ if (jsonLike) {
55
+ try {
56
+ return `${JSON.stringify(stripSafeToWrite(JSON.parse(trimmed)), null, 2)}
57
+ `;
58
+ } catch {
59
+ }
60
+ }
61
+ return trimmed.endsWith("\n") ? trimmed : `${trimmed}
62
+ `;
63
+ }
64
+ function looksLikeJson(text) {
65
+ const start = text.trimStart()[0];
66
+ return start === "{" || start === "[";
67
+ }
68
+
69
+ // src/const.ts
70
+ var DEFAULT_BASE_URL = "https://api.kingminos.com";
71
+ var TOKEN_ENV = "KINGMINOS_API_KEY";
72
+ var BASE_URL_ENV = "KINGMINOS_BASE_URL";
73
+ var BYOK_PROVIDERS = [
74
+ "zoominfo",
75
+ "leadmagic",
76
+ "findymail",
77
+ "wiza",
78
+ "aiark",
79
+ "builtwith",
80
+ "prospeo",
81
+ "apollo",
82
+ "companyurlfinder",
83
+ "peopledatalabs",
84
+ "hginsights",
85
+ "brandfetch",
86
+ "enrichcrm",
87
+ "lemlist",
88
+ "snovio",
89
+ "cognism",
90
+ "lusha",
91
+ "starbridge",
92
+ "clay",
93
+ "databar",
94
+ "quickenrich",
95
+ "getleads",
96
+ "blitzapi"
97
+ ];
98
+ function isByokProvider(value) {
99
+ return BYOK_PROVIDERS.includes(value);
100
+ }
101
+ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set(["help", "h", "dry-run", "skip-cache", "json"]);
102
+ var GLOBAL_FLAGS = /* @__PURE__ */ new Set(["token", "base-url", "help", "h", "json"]);
103
+ var EXIT = {
104
+ ok: 0,
105
+ usage: 1,
106
+ unauthorized: 2,
107
+ payment: 3,
108
+ rateLimited: 4,
109
+ server: 5,
110
+ http: 6
111
+ };
112
+
113
+ // src/usage.ts
114
+ var UsageError = class extends Error {
115
+ constructor(message) {
116
+ super(message);
117
+ this.name = "UsageError";
118
+ }
119
+ };
120
+ function assertNever(value, message) {
121
+ throw new Error(message ?? `unexpected value: ${String(value)}`);
122
+ }
123
+
124
+ // src/parse.ts
125
+ var COMMAND_FLAGS = {
126
+ health: /* @__PURE__ */ new Set(),
127
+ openapi: /* @__PURE__ */ new Set(),
128
+ providers: /* @__PURE__ */ new Set(),
129
+ capabilities: /* @__PURE__ */ new Set(),
130
+ recipes: /* @__PURE__ */ new Set(),
131
+ batch: /* @__PURE__ */ new Set(["file", "priority", "explain", "schema", "dry-run", "path"]),
132
+ "company.resolve": /* @__PURE__ */ new Set([
133
+ "body",
134
+ "email",
135
+ "name",
136
+ "domain",
137
+ "website",
138
+ "dry-run",
139
+ "external-ref",
140
+ "routing",
141
+ "compliance",
142
+ "schema-version",
143
+ "idempotency-key",
144
+ "explain",
145
+ "schema",
146
+ "max-provider-credits",
147
+ "path",
148
+ "preset",
149
+ "priority"
150
+ ]),
151
+ "company.domain": /* @__PURE__ */ new Set([
152
+ "body",
153
+ "email",
154
+ "name",
155
+ "company-name",
156
+ "domain",
157
+ "website",
158
+ "city",
159
+ "state",
160
+ "country",
161
+ "postal-code",
162
+ "phone",
163
+ "linkedin-url",
164
+ "family-policy",
165
+ "path",
166
+ "preset",
167
+ "skip-cache",
168
+ "dry-run",
169
+ "external-ref",
170
+ "routing",
171
+ "compliance",
172
+ "schema-version",
173
+ "idempotency-key",
174
+ "explain",
175
+ "schema",
176
+ "max-provider-credits",
177
+ "cache",
178
+ "cache-control",
179
+ "priority"
180
+ ]),
181
+ "company.b2b_social": /* @__PURE__ */ new Set([
182
+ "body",
183
+ "email",
184
+ "name",
185
+ "domain",
186
+ "website",
187
+ "linkedin-url",
188
+ "hint",
189
+ "country",
190
+ "path",
191
+ "preset",
192
+ "dry-run",
193
+ "external-ref",
194
+ "routing",
195
+ "compliance",
196
+ "idempotency-key",
197
+ "explain",
198
+ "schema",
199
+ "max-provider-credits",
200
+ "priority"
201
+ ]),
202
+ "company.icon": /* @__PURE__ */ new Set([
203
+ "body",
204
+ "email",
205
+ "name",
206
+ "domain",
207
+ "website",
208
+ "dry-run",
209
+ "external-ref",
210
+ "compliance",
211
+ "idempotency-key",
212
+ "explain",
213
+ "schema"
214
+ ]),
215
+ "company.description": /* @__PURE__ */ new Set([
216
+ "body",
217
+ "email",
218
+ "name",
219
+ "domain",
220
+ "website",
221
+ "path",
222
+ "preset",
223
+ "max-chars",
224
+ "skip-cache",
225
+ "dry-run",
226
+ "external-ref",
227
+ "routing",
228
+ "compliance",
229
+ "idempotency-key",
230
+ "explain",
231
+ "schema",
232
+ "max-provider-credits",
233
+ "cache",
234
+ "cache-control",
235
+ "priority"
236
+ ]),
237
+ "company.hierarchy": /* @__PURE__ */ new Set([
238
+ "body",
239
+ "email",
240
+ "name",
241
+ "domain",
242
+ "website",
243
+ "country",
244
+ "path",
245
+ "preset",
246
+ "dry-run",
247
+ "external-ref",
248
+ "routing",
249
+ "compliance",
250
+ "idempotency-key",
251
+ "explain",
252
+ "schema",
253
+ "max-provider-credits",
254
+ "priority"
255
+ ]),
256
+ "person.verify_employment": /* @__PURE__ */ new Set([
257
+ "body",
258
+ "email",
259
+ "first-name",
260
+ "last-name",
261
+ "linkedin-url",
262
+ "expected-company",
263
+ "expected-company-domain",
264
+ "expected-company-name",
265
+ "expected-company-website",
266
+ "dry-run",
267
+ "external-ref",
268
+ "routing",
269
+ "compliance",
270
+ "idempotency-key",
271
+ "explain",
272
+ "schema",
273
+ "max-provider-credits",
274
+ "priority"
275
+ ]),
276
+ "person.language": /* @__PURE__ */ new Set([
277
+ "body",
278
+ "email",
279
+ "first-name",
280
+ "last-name",
281
+ "linkedin-url",
282
+ "skip-cache",
283
+ "dry-run",
284
+ "external-ref",
285
+ "compliance",
286
+ "idempotency-key",
287
+ "explain",
288
+ "schema",
289
+ "priority"
290
+ ]),
291
+ "person.phone": /* @__PURE__ */ new Set([
292
+ "body",
293
+ "email",
294
+ "first-name",
295
+ "last-name",
296
+ "linkedin-url",
297
+ "domain",
298
+ "phone-types",
299
+ "max-results",
300
+ "format",
301
+ "skip-cache",
302
+ "dry-run",
303
+ "external-ref",
304
+ "compliance",
305
+ "idempotency-key",
306
+ "explain",
307
+ "schema",
308
+ "cache",
309
+ "cache-control",
310
+ "priority"
311
+ ]),
312
+ "person.title": /* @__PURE__ */ new Set([
313
+ "body",
314
+ "email",
315
+ "first-name",
316
+ "last-name",
317
+ "linkedin-url",
318
+ "company-domain",
319
+ "fields",
320
+ "path",
321
+ "routing",
322
+ "skip-cache",
323
+ "dry-run",
324
+ "external-ref",
325
+ "compliance",
326
+ "idempotency-key",
327
+ "explain",
328
+ "schema",
329
+ "cache",
330
+ "cache-control",
331
+ "priority"
332
+ ]),
333
+ "person.name": /* @__PURE__ */ new Set([
334
+ "body",
335
+ "email",
336
+ "linkedin-url",
337
+ "path",
338
+ "routing",
339
+ "skip-cache",
340
+ "dry-run",
341
+ "external-ref",
342
+ "compliance",
343
+ "idempotency-key",
344
+ "explain",
345
+ "schema",
346
+ "cache",
347
+ "cache-control",
348
+ "priority"
349
+ ]),
350
+ "person.b2b_social": /* @__PURE__ */ new Set([
351
+ "body",
352
+ "email",
353
+ "first-name",
354
+ "last-name",
355
+ "linkedin-url",
356
+ "path",
357
+ "routing",
358
+ "skip-cache",
359
+ "dry-run",
360
+ "external-ref",
361
+ "compliance",
362
+ "idempotency-key",
363
+ "explain",
364
+ "schema",
365
+ "cache",
366
+ "cache-control",
367
+ "priority"
368
+ ]),
369
+ "runs.get": /* @__PURE__ */ new Set(["explain", "schema"]),
370
+ "credentials.put": /* @__PURE__ */ new Set(["body", "api-key", "client-id", "client-secret"]),
371
+ "credentials.delete": /* @__PURE__ */ new Set(),
372
+ "subjects.delete": /* @__PURE__ */ new Set(),
373
+ help: /* @__PURE__ */ new Set()
374
+ };
375
+ var KNOWN_FLAGS = /* @__PURE__ */ new Set([
376
+ ...GLOBAL_FLAGS,
377
+ ...BOOLEAN_FLAGS,
378
+ ...Object.values(COMMAND_FLAGS).flatMap((names) => [...names])
379
+ ]);
380
+ function parseArgv(argv) {
381
+ const positionals = [];
382
+ const flags = {};
383
+ for (let i = 0; i < argv.length; i += 1) {
384
+ const arg = argv[i];
385
+ if (arg === void 0) {
386
+ break;
387
+ }
388
+ if (arg === "--") {
389
+ positionals.push(...argv.slice(i + 1));
390
+ break;
391
+ }
392
+ if (arg.startsWith("--")) {
393
+ const eq = arg.indexOf("=");
394
+ const rawName = eq === -1 ? arg.slice(2) : arg.slice(2, eq);
395
+ const name = rawName.trim();
396
+ if (!name) {
397
+ throw new UsageError("empty flag");
398
+ }
399
+ if (!KNOWN_FLAGS.has(name)) {
400
+ throw new UsageError(`unknown flag --${name}`);
401
+ }
402
+ if (BOOLEAN_FLAGS.has(name)) {
403
+ if (eq !== -1) {
404
+ throw new UsageError(`--${name} does not take a value`);
405
+ }
406
+ flags[name] = true;
407
+ continue;
408
+ }
409
+ const value = eq === -1 ? argv[i + 1] : arg.slice(eq + 1);
410
+ if (eq === -1) {
411
+ i += 1;
412
+ }
413
+ if (value === void 0 || eq === -1 && value.startsWith("--")) {
414
+ throw new UsageError(`--${name} requires a value`);
415
+ }
416
+ flags[name] = value;
417
+ continue;
418
+ }
419
+ if (arg === "-h") {
420
+ flags.help = true;
421
+ continue;
422
+ }
423
+ if (arg.startsWith("-")) {
424
+ throw new UsageError(`unknown flag ${arg}`);
425
+ }
426
+ positionals.push(arg);
427
+ }
428
+ return { positionals, flags };
429
+ }
430
+ function resolveCommand(positionals) {
431
+ if (positionals.length === 0) {
432
+ return { name: "help" };
433
+ }
434
+ const [head, mid, tail, ...rest] = positionals;
435
+ if (rest.length > 0) {
436
+ throw new UsageError(`unexpected arguments: ${rest.join(" ")}`);
437
+ }
438
+ switch (head) {
439
+ case "help":
440
+ return { name: "help", topic: mid };
441
+ case "health":
442
+ case "openapi":
443
+ case "providers":
444
+ case "capabilities":
445
+ case "recipes":
446
+ if (mid !== void 0) {
447
+ throw new UsageError(`${head} does not take arguments`);
448
+ }
449
+ return { name: head };
450
+ case "batch":
451
+ return resolveBatch(mid, tail);
452
+ case "company":
453
+ return resolveCompany(mid, tail);
454
+ case "person":
455
+ if (mid !== "verify-employment" && mid !== "language" && mid !== "phone" && mid !== "title" && mid !== "name" && mid !== "b2b-social") {
456
+ throw new UsageError(
457
+ "person commands: verify-employment | language | phone | title | name | b2b-social"
458
+ );
459
+ }
460
+ if (tail !== void 0) {
461
+ throw new UsageError(`person ${mid} does not take positional arguments`);
462
+ }
463
+ if (mid === "language") {
464
+ return { name: "person.language" };
465
+ }
466
+ if (mid === "phone") {
467
+ return { name: "person.phone" };
468
+ }
469
+ if (mid === "title") {
470
+ return { name: "person.title" };
471
+ }
472
+ if (mid === "name") {
473
+ return { name: "person.name" };
474
+ }
475
+ if (mid === "b2b-social") {
476
+ return { name: "person.b2b_social" };
477
+ }
478
+ return { name: "person.verify_employment" };
479
+ case "runs":
480
+ if (mid !== "get" || !tail) {
481
+ throw new UsageError("runs commands: get <id>");
482
+ }
483
+ return { name: "runs.get", id: tail };
484
+ case "credentials":
485
+ if (mid !== "put" && mid !== "delete" || !tail) {
486
+ throw new UsageError("credentials commands: put <provider> | delete <provider>");
487
+ }
488
+ return mid === "put" ? { name: "credentials.put", provider: tail } : { name: "credentials.delete", provider: tail };
489
+ case "subjects":
490
+ if (mid !== "delete" || !tail) {
491
+ throw new UsageError("subjects commands: delete <subject_key>");
492
+ }
493
+ return { name: "subjects.delete", subjectKey: tail };
494
+ default:
495
+ throw new UsageError(`unknown command ${head}`);
496
+ }
497
+ }
498
+ function resolveBatch(mid, tail) {
499
+ if (mid === "company") {
500
+ const inner = resolveCompany(tail, void 0);
501
+ switch (inner.name) {
502
+ case "company.resolve":
503
+ case "company.domain":
504
+ case "company.hierarchy":
505
+ case "company.b2b_social":
506
+ case "company.icon":
507
+ case "company.description":
508
+ return { name: "batch", capability: inner.name };
509
+ default:
510
+ throw new UsageError(
511
+ "batch company commands: resolve | domain | hierarchy | b2b-social | icon | description"
512
+ );
513
+ }
514
+ }
515
+ if (mid === "person") {
516
+ if (tail !== "verify-employment" && tail !== "language" && tail !== "phone" && tail !== "title" && tail !== "name" && tail !== "b2b-social") {
517
+ throw new UsageError(
518
+ "batch person commands: verify-employment | language | phone | title | name | b2b-social"
519
+ );
520
+ }
521
+ if (tail === "language") {
522
+ return { name: "batch", capability: "person.language" };
523
+ }
524
+ if (tail === "phone") {
525
+ return { name: "batch", capability: "person.phone" };
526
+ }
527
+ if (tail === "title") {
528
+ return { name: "batch", capability: "person.title" };
529
+ }
530
+ if (tail === "name") {
531
+ return { name: "batch", capability: "person.name" };
532
+ }
533
+ if (tail === "b2b-social") {
534
+ return { name: "batch", capability: "person.b2b_social" };
535
+ }
536
+ return { name: "batch", capability: "person.verify_employment" };
537
+ }
538
+ throw new UsageError(
539
+ "batch commands: batch <company|person> <command> --file <jsonl> --priority speed|balance|accuracy|coverage"
540
+ );
541
+ }
542
+ function resolveCompany(mid, tail) {
543
+ if (tail !== void 0) {
544
+ throw new UsageError(
545
+ "company <resolve|domain|hierarchy|b2b-social|icon|description> does not take positional arguments"
546
+ );
547
+ }
548
+ switch (mid) {
549
+ case "resolve":
550
+ return { name: "company.resolve" };
551
+ case "domain":
552
+ return { name: "company.domain" };
553
+ case "hierarchy":
554
+ return { name: "company.hierarchy" };
555
+ case "b2b-social":
556
+ return { name: "company.b2b_social" };
557
+ case "icon":
558
+ return { name: "company.icon" };
559
+ case "description":
560
+ return { name: "company.description" };
561
+ default:
562
+ throw new UsageError(
563
+ "company commands: resolve | domain | hierarchy | b2b-social | icon | description"
564
+ );
565
+ }
566
+ }
567
+ function assertKnownFlags(commandName, flags) {
568
+ const allowed = COMMAND_FLAGS[commandName];
569
+ if (!allowed) {
570
+ throw new UsageError(`unknown command ${commandName}`);
571
+ }
572
+ for (const name of Object.keys(flags)) {
573
+ if (GLOBAL_FLAGS.has(name) || allowed.has(name)) {
574
+ continue;
575
+ }
576
+ throw new UsageError(`unknown flag --${name}`);
577
+ }
578
+ }
579
+ function flagString(flags, name) {
580
+ const value = flags[name];
581
+ if (value === void 0 || value === true) {
582
+ return void 0;
583
+ }
584
+ return value;
585
+ }
586
+ function flagBool(flags, name) {
587
+ return flags[name] === true ? true : void 0;
588
+ }
589
+
590
+ // src/priority.ts
591
+ var PRIORITY_HELP = "Before a bulk run, ask which path: speed, balance, accuracy, or coverage. Re-run with --priority speed|balance|accuracy|coverage. Max yield = coverage. company.resolve value is the one-step lookup, not a tier.";
592
+ function parsePriority(value) {
593
+ switch (value) {
594
+ case "speed":
595
+ case "balance":
596
+ case "accuracy":
597
+ case "coverage":
598
+ return value;
599
+ default:
600
+ throw new UsageError(PRIORITY_HELP);
601
+ }
602
+ }
603
+ function pathForPriority(priority) {
604
+ return priority;
605
+ }
606
+ function applyPriority(body, priority, _capability) {
607
+ const path = pathForPriority(priority);
608
+ const routing = body.routing && typeof body.routing === "object" && !Array.isArray(body.routing) ? body.routing : {};
609
+ return { ...body, routing: { ...routing, path } };
610
+ }
611
+ function formatDecisionNotes(body) {
612
+ if (!body || typeof body !== "object" || Array.isArray(body)) {
613
+ return "";
614
+ }
615
+ const record = body;
616
+ const lines = [];
617
+ const confidence = record.answer?.confidence;
618
+ if (confidence === "high" || confidence === "medium" || confidence === "low") {
619
+ lines.push(`Confidence: ${confidence}`);
620
+ } else if (confidence === null) {
621
+ lines.push("Confidence: none");
622
+ }
623
+ const spent = record.usage?.credits?.spent;
624
+ if (typeof spent === "number") {
625
+ lines.push(`Credits spent: ${spent}`);
626
+ }
627
+ return lines.length > 0 ? `
628
+ ${lines.join("\n")}
629
+ ` : "";
630
+ }
631
+ function formatNextStepsHints(body) {
632
+ if (!body || typeof body !== "object" || Array.isArray(body)) {
633
+ return "";
634
+ }
635
+ const steps = body.next_steps;
636
+ if (!Array.isArray(steps) || steps.length === 0) {
637
+ return "";
638
+ }
639
+ const lines = ["", "Next:"];
640
+ for (const raw of steps) {
641
+ if (!raw || typeof raw !== "object") {
642
+ continue;
643
+ }
644
+ const step = raw;
645
+ const credits = typeof step.est_credits === "number" ? ` (~${step.est_credits} credits)` : "";
646
+ const target = step.capability ? ` ${step.capability}` : "";
647
+ lines.push(`- ${step.action ?? "next"}${target}${credits}: ${step.reason ?? ""}`.trim());
648
+ if (step.suggested && typeof step.suggested === "object") {
649
+ lines.push(` suggested: ${JSON.stringify(step.suggested)}`);
650
+ }
651
+ }
652
+ return `${lines.join("\n")}
653
+ `;
654
+ }
655
+
656
+ // src/commands.ts
657
+ function buildRequest(command, flags, loader) {
658
+ switch (command.name) {
659
+ case "help":
660
+ throw new UsageError("help is not an HTTP call");
661
+ case "batch":
662
+ throw new UsageError("batch is not a single HTTP call");
663
+ case "health":
664
+ return { method: "GET", path: "/health", headers: {}, auth: false };
665
+ case "openapi":
666
+ return { method: "GET", path: "/openapi.yaml", headers: {}, auth: false };
667
+ case "providers":
668
+ return { method: "GET", path: "/v1/providers", headers: {}, auth: true };
669
+ case "capabilities":
670
+ return { method: "GET", path: "/v1/capabilities", headers: {}, auth: true };
671
+ case "recipes":
672
+ return { method: "GET", path: "/v1/recipes", headers: {}, auth: true };
673
+ case "company.resolve":
674
+ return capabilityPost("/v1/company/resolve", resolveBody(flags, loader), flags);
675
+ case "company.domain":
676
+ return capabilityPost("/v1/company/domain", domainBody(flags, loader), flags, {
677
+ cache: true
678
+ });
679
+ case "company.hierarchy":
680
+ return capabilityPost("/v1/company/hierarchy", hierarchyBody(flags, loader), flags);
681
+ case "company.b2b_social":
682
+ return capabilityPost("/v1/company/b2b-social", b2bSocialBody(flags, loader), flags);
683
+ case "company.icon":
684
+ return capabilityPost("/v1/company/icon", iconBody(flags, loader), flags);
685
+ case "company.description":
686
+ return capabilityPost("/v1/company/description", descriptionBody(flags, loader), flags, {
687
+ cache: true
688
+ });
689
+ case "person.verify_employment":
690
+ return capabilityPost("/v1/person/verify-employment", employmentBody(flags, loader), flags);
691
+ case "person.language":
692
+ return capabilityPost("/v1/person/language", languageBody(flags, loader), flags);
693
+ case "person.phone":
694
+ return capabilityPost("/v1/person/phone", phoneBody(flags, loader), flags, {
695
+ cache: true
696
+ });
697
+ case "person.title":
698
+ return capabilityPost("/v1/person/title", titleBody(flags, loader), flags, {
699
+ cache: true
700
+ });
701
+ case "person.name":
702
+ return capabilityPost("/v1/person/name", nameBody(flags, loader), flags, {
703
+ cache: true
704
+ });
705
+ case "person.b2b_social":
706
+ return capabilityPost("/v1/person/b2b-social", personB2bSocialBody(flags, loader), flags, {
707
+ cache: true
708
+ });
709
+ case "runs.get":
710
+ return {
711
+ method: "GET",
712
+ path: `/v1/runs/${encodeURIComponent(command.id)}`,
713
+ headers: explainSchemaHeaders(flags),
714
+ auth: true
715
+ };
716
+ case "credentials.put":
717
+ return {
718
+ method: "PUT",
719
+ path: `/v1/credentials/${encodeURIComponent(parseProvider(command.provider))}`,
720
+ headers: {},
721
+ body: credentialsPutBody(command.provider, flags, loader),
722
+ auth: true
723
+ };
724
+ case "credentials.delete":
725
+ return {
726
+ method: "DELETE",
727
+ path: `/v1/credentials/${encodeURIComponent(parseProvider(command.provider))}`,
728
+ headers: {},
729
+ auth: true
730
+ };
731
+ case "subjects.delete":
732
+ return {
733
+ method: "DELETE",
734
+ path: `/v1/subjects/${encodeURIComponent(command.subjectKey)}`,
735
+ headers: {},
736
+ auth: true
737
+ };
738
+ default:
739
+ return assertNever(command);
740
+ }
741
+ }
742
+ function capabilityPost(path, body, flags, options) {
743
+ return {
744
+ method: "POST",
745
+ path,
746
+ headers: {
747
+ ...explainSchemaHeaders(flags),
748
+ ...optionalHeader("Idempotency-Key", flagString(flags, "idempotency-key")),
749
+ ...optionalHeader("X-Router-Max-Provider-Credits", flagString(flags, "max-provider-credits")),
750
+ ...options?.cache ? {
751
+ ...optionalHeader("X-Router-Cache", flagString(flags, "cache")),
752
+ ...optionalHeader("Cache-Control", flagString(flags, "cache-control"))
753
+ } : {}
754
+ },
755
+ body,
756
+ auth: true
757
+ };
758
+ }
759
+ function explainSchemaHeaders(flags) {
760
+ return {
761
+ ...optionalHeader("X-Router-Explain", flagString(flags, "explain")),
762
+ ...optionalHeader("X-Router-Schema", flagString(flags, "schema"))
763
+ };
764
+ }
765
+ function optionalHeader(name, value) {
766
+ return value === void 0 ? {} : { [name]: value };
767
+ }
768
+ function resolveBody(flags, loader) {
769
+ const body = baseBody(flags, loader);
770
+ assignString(body, "email", flags);
771
+ assignString(body, "name", flags);
772
+ assignString(body, "domain", flags);
773
+ assignString(body, "website", flags);
774
+ assignCommon(body, flags);
775
+ assignJson(body, "routing", flags);
776
+ assignJson(body, "compliance", flags);
777
+ assignString(body, "schema_version", flags, "schema-version");
778
+ assignString(body, "path", flags);
779
+ assignString(body, "preset", flags);
780
+ return withPriority(body, flags, "company.resolve");
781
+ }
782
+ function domainBody(flags, loader) {
783
+ const body = baseBody(flags, loader);
784
+ assignString(body, "email", flags);
785
+ assignString(body, "name", flags);
786
+ assignString(body, "company_name", flags, "company-name");
787
+ assignString(body, "domain", flags);
788
+ assignString(body, "website", flags);
789
+ assignString(body, "city", flags);
790
+ assignString(body, "state", flags);
791
+ assignString(body, "country", flags);
792
+ assignString(body, "postal_code", flags, "postal-code");
793
+ assignString(body, "phone", flags);
794
+ assignString(body, "linkedin_url", flags, "linkedin-url");
795
+ assignString(body, "family_policy", flags, "family-policy");
796
+ assignString(body, "path", flags);
797
+ assignString(body, "preset", flags);
798
+ if (flagBool(flags, "skip-cache")) {
799
+ body.skip_cache = true;
800
+ }
801
+ assignCommon(body, flags);
802
+ assignJson(body, "routing", flags);
803
+ assignJson(body, "compliance", flags);
804
+ assignString(body, "schema_version", flags, "schema-version");
805
+ return withPriority(body, flags, "company.domain");
806
+ }
807
+ function b2bSocialBody(flags, loader) {
808
+ const body = baseBody(flags, loader);
809
+ assignString(body, "email", flags);
810
+ assignString(body, "name", flags);
811
+ assignString(body, "domain", flags);
812
+ assignString(body, "website", flags);
813
+ assignString(body, "linkedin_url", flags, "linkedin-url");
814
+ assignString(body, "hint", flags);
815
+ assignString(body, "country", flags);
816
+ assignString(body, "path", flags);
817
+ assignString(body, "preset", flags);
818
+ assignCommon(body, flags);
819
+ assignJson(body, "routing", flags);
820
+ assignJson(body, "compliance", flags);
821
+ return withPriority(body, flags, "company.b2b_social");
822
+ }
823
+ function iconBody(flags, loader) {
824
+ const body = baseBody(flags, loader);
825
+ assignString(body, "email", flags);
826
+ assignString(body, "name", flags);
827
+ assignString(body, "domain", flags);
828
+ assignString(body, "website", flags);
829
+ assignCommon(body, flags);
830
+ assignJson(body, "compliance", flags);
831
+ return body;
832
+ }
833
+ function descriptionBody(flags, loader) {
834
+ const body = baseBody(flags, loader);
835
+ assignString(body, "email", flags);
836
+ assignString(body, "name", flags);
837
+ assignString(body, "domain", flags);
838
+ assignString(body, "website", flags);
839
+ assignString(body, "path", flags);
840
+ assignString(body, "preset", flags);
841
+ assignInteger(body, "max_chars", flags, "max-chars", 80, 500);
842
+ if (flagBool(flags, "skip-cache")) {
843
+ body.skip_cache = true;
844
+ }
845
+ assignCommon(body, flags);
846
+ assignJson(body, "routing", flags);
847
+ assignJson(body, "compliance", flags);
848
+ return withPriority(body, flags, "company.description");
849
+ }
850
+ function hierarchyBody(flags, loader) {
851
+ const body = baseBody(flags, loader);
852
+ assignString(body, "email", flags);
853
+ assignString(body, "name", flags);
854
+ assignString(body, "domain", flags);
855
+ assignString(body, "website", flags);
856
+ assignString(body, "country", flags);
857
+ assignString(body, "path", flags);
858
+ assignString(body, "preset", flags);
859
+ assignCommon(body, flags);
860
+ assignJson(body, "routing", flags);
861
+ assignJson(body, "compliance", flags);
862
+ return withPriority(body, flags, "company.hierarchy");
863
+ }
864
+ function employmentBody(flags, loader) {
865
+ const body = baseBody(flags, loader);
866
+ assignString(body, "email", flags);
867
+ assignString(body, "first_name", flags, "first-name");
868
+ assignString(body, "last_name", flags, "last-name");
869
+ assignString(body, "linkedin_url", flags, "linkedin-url");
870
+ const expected = expectedCompany(flags);
871
+ if (expected) {
872
+ body.expected_company = expected;
873
+ }
874
+ assignCommon(body, flags);
875
+ assignJson(body, "routing", flags);
876
+ assignJson(body, "compliance", flags);
877
+ return withPriority(body, flags, "person.verify_employment");
878
+ }
879
+ function languageBody(flags, loader) {
880
+ const body = baseBody(flags, loader);
881
+ assignString(body, "email", flags);
882
+ assignString(body, "linkedin_url", flags, "linkedin-url");
883
+ assignString(body, "first_name", flags, "first-name");
884
+ assignString(body, "last_name", flags, "last-name");
885
+ if (flagBool(flags, "skip-cache")) {
886
+ body.skip_cache = true;
887
+ }
888
+ assignCommon(body, flags);
889
+ assignJson(body, "compliance", flags);
890
+ return withPriority(body, flags, "person.language");
891
+ }
892
+ function phoneBody(flags, loader) {
893
+ const body = baseBody(flags, loader);
894
+ assignString(body, "email", flags);
895
+ assignString(body, "linkedin_url", flags, "linkedin-url");
896
+ assignString(body, "first_name", flags, "first-name");
897
+ assignString(body, "last_name", flags, "last-name");
898
+ assignString(body, "domain", flags);
899
+ const phoneTypes = flagString(flags, "phone-types");
900
+ if (phoneTypes !== void 0) {
901
+ const types = phoneTypes.split(",").map((type) => type.trim()).filter(Boolean);
902
+ if (types.length > 0) {
903
+ body.phone_types = types;
904
+ }
905
+ }
906
+ const maxResults = Number(flagString(flags, "max-results"));
907
+ if (Number.isInteger(maxResults) && maxResults >= 1) {
908
+ body.max_results = maxResults;
909
+ }
910
+ assignString(body, "format", flags);
911
+ if (flagBool(flags, "skip-cache")) {
912
+ body.skip_cache = true;
913
+ }
914
+ assignCommon(body, flags);
915
+ assignJson(body, "compliance", flags);
916
+ return withPriority(body, flags, "person.phone");
917
+ }
918
+ function titleBody(flags, loader) {
919
+ const body = baseBody(flags, loader);
920
+ assignString(body, "email", flags);
921
+ assignString(body, "linkedin_url", flags, "linkedin-url");
922
+ assignString(body, "first_name", flags, "first-name");
923
+ assignString(body, "last_name", flags, "last-name");
924
+ assignString(body, "company_domain", flags, "company-domain");
925
+ const fields = flagString(flags, "fields");
926
+ if (fields !== void 0) {
927
+ const list = fields.split(",").map((field) => field.trim()).filter(Boolean);
928
+ if (list.length > 0) {
929
+ body.fields = list;
930
+ }
931
+ }
932
+ if (flagBool(flags, "skip-cache")) {
933
+ body.skip_cache = true;
934
+ }
935
+ assignCommon(body, flags);
936
+ assignRouting(body, flags);
937
+ assignJson(body, "compliance", flags);
938
+ return withPriority(body, flags, "person.title");
939
+ }
940
+ function nameBody(flags, loader) {
941
+ const body = baseBody(flags, loader);
942
+ assignString(body, "email", flags);
943
+ assignString(body, "linkedin_url", flags, "linkedin-url");
944
+ if (flagBool(flags, "skip-cache")) {
945
+ body.skip_cache = true;
946
+ }
947
+ assignCommon(body, flags);
948
+ assignRouting(body, flags);
949
+ assignJson(body, "compliance", flags);
950
+ return withPriority(body, flags, "person.name");
951
+ }
952
+ function personB2bSocialBody(flags, loader) {
953
+ const body = baseBody(flags, loader);
954
+ assignString(body, "email", flags);
955
+ assignString(body, "linkedin_url", flags, "linkedin-url");
956
+ assignString(body, "first_name", flags, "first-name");
957
+ assignString(body, "last_name", flags, "last-name");
958
+ if (flagBool(flags, "skip-cache")) {
959
+ body.skip_cache = true;
960
+ }
961
+ assignCommon(body, flags);
962
+ assignRouting(body, flags);
963
+ assignJson(body, "compliance", flags);
964
+ return withPriority(body, flags, "person.b2b_social");
965
+ }
966
+ function assignRouting(body, flags) {
967
+ assignJson(body, "routing", flags);
968
+ const path = flagString(flags, "path");
969
+ if (path !== void 0) {
970
+ const routing = body.routing && typeof body.routing === "object" && !Array.isArray(body.routing) ? body.routing : {};
971
+ body.routing = { ...routing, path };
972
+ }
973
+ }
974
+ function expectedCompany(flags) {
975
+ const fromJson = parseJsonFlag(flags, "expected-company");
976
+ const company = fromJson ? { ...fromJson } : {};
977
+ const domain = flagString(flags, "expected-company-domain");
978
+ const name = flagString(flags, "expected-company-name");
979
+ const website = flagString(flags, "expected-company-website");
980
+ if (domain !== void 0) {
981
+ company.domain = domain;
982
+ }
983
+ if (name !== void 0) {
984
+ company.name = name;
985
+ }
986
+ if (website !== void 0) {
987
+ company.website = website;
988
+ }
989
+ return Object.keys(company).length > 0 ? company : void 0;
990
+ }
991
+ function withPriority(body, flags, capability) {
992
+ const raw = flagString(flags, "priority");
993
+ if (raw === void 0) {
994
+ return body;
995
+ }
996
+ return applyPriority(body, parsePriority(raw), capability);
997
+ }
998
+ function assignCommon(body, flags) {
999
+ if (flagBool(flags, "dry-run")) {
1000
+ body.dry_run = true;
1001
+ }
1002
+ assignString(body, "external_ref", flags, "external-ref");
1003
+ }
1004
+ function assignString(body, field, flags, flagName = field.replaceAll("_", "-")) {
1005
+ const value = flagString(flags, flagName);
1006
+ if (value !== void 0) {
1007
+ body[field] = value;
1008
+ }
1009
+ }
1010
+ function assignInteger(body, field, flags, flagName, min, max) {
1011
+ const raw = flagString(flags, flagName);
1012
+ if (raw === void 0) {
1013
+ return;
1014
+ }
1015
+ const value = Number(raw);
1016
+ if (!Number.isInteger(value)) {
1017
+ throw new UsageError(`--${flagName} must be an integer`);
1018
+ }
1019
+ if (value < min || value > max) {
1020
+ throw new UsageError(`--${flagName} must be between ${min} and ${max}`);
1021
+ }
1022
+ body[field] = value;
1023
+ }
1024
+ function assignJson(body, field, flags) {
1025
+ const parsed = parseJsonFlag(flags, field);
1026
+ if (parsed) {
1027
+ body[field] = parsed;
1028
+ }
1029
+ }
1030
+ function parseJsonFlag(flags, name) {
1031
+ const raw = flagString(flags, name);
1032
+ if (raw === void 0) {
1033
+ return void 0;
1034
+ }
1035
+ return parseObjectJson(raw, `--${name}`);
1036
+ }
1037
+ function baseBody(flags, loader) {
1038
+ const raw = flagString(flags, "body");
1039
+ if (raw === void 0) {
1040
+ return {};
1041
+ }
1042
+ return { ...loadBodyObject(raw, loader) };
1043
+ }
1044
+ function loadBodyObject(raw, loader) {
1045
+ if (raw === "-") {
1046
+ return parseObjectJson(loader.stdin, "--body -");
1047
+ }
1048
+ if (raw.startsWith("@")) {
1049
+ const path = raw.slice(1);
1050
+ if (!path) {
1051
+ throw new UsageError("--body @file requires a path");
1052
+ }
1053
+ return parseObjectJson(loader.readFile(path), `--body @${path}`);
1054
+ }
1055
+ return parseObjectJson(raw, "--body");
1056
+ }
1057
+ function parseObjectJson(raw, label) {
1058
+ let parsed;
1059
+ try {
1060
+ parsed = JSON.parse(raw);
1061
+ } catch {
1062
+ throw new UsageError(`${label} is not valid JSON`);
1063
+ }
1064
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
1065
+ throw new UsageError(`${label} must be a JSON object`);
1066
+ }
1067
+ return parsed;
1068
+ }
1069
+ function parseProvider(provider) {
1070
+ if (provider === "websearch") {
1071
+ throw new UsageError("websearch is house-only (byok_not_supported)");
1072
+ }
1073
+ if (!isByokProvider(provider)) {
1074
+ throw new UsageError(`unknown_provider: ${provider}`);
1075
+ }
1076
+ return provider;
1077
+ }
1078
+ function credentialsPutBody(provider, flags, loader) {
1079
+ const rawBody = flagString(flags, "body");
1080
+ const fromFile = rawBody ? loadBodyObject(rawBody, loader) : void 0;
1081
+ if (provider === "zoominfo") {
1082
+ const clientId = flagString(flags, "client-id") ?? stringField(fromFile, "clientId");
1083
+ const clientSecret = flagString(flags, "client-secret") ?? stringField(fromFile, "clientSecret");
1084
+ if (!clientId || !clientSecret) {
1085
+ throw new UsageError(
1086
+ "credentials put zoominfo requires --client-id and --client-secret (or --body { clientId, clientSecret })"
1087
+ );
1088
+ }
1089
+ return { clientId, clientSecret };
1090
+ }
1091
+ if (fromFile && flagString(flags, "api-key") === void 0 && (stringField(fromFile, "apiKey") || stringField(fromFile, "api_key"))) {
1092
+ return fromFile;
1093
+ }
1094
+ const apiKey = flagString(flags, "api-key") ?? stringField(fromFile, "apiKey") ?? stringField(fromFile, "api_key");
1095
+ if (!apiKey) {
1096
+ throw new UsageError(`credentials put ${provider} requires --api-key (or --body { apiKey })`);
1097
+ }
1098
+ return { apiKey };
1099
+ }
1100
+ function stringField(body, key) {
1101
+ const value = body?.[key];
1102
+ return typeof value === "string" && value !== "" ? value : void 0;
1103
+ }
1104
+
1105
+ // src/help.ts
1106
+ var ROOT_HELP = `King Minos CLI \u2014 KingMinos by OutboundSync
1107
+
1108
+ Usage:
1109
+ kingminos [--token <key>] [--base-url <url>] [--json] <command>
1110
+
1111
+ Auth:
1112
+ Pass a tenant Bearer with --token or ${TOKEN_ENV}.
1113
+ Tenant keys use the osr_ prefix. App-minted user tokens use km_.
1114
+ Mint a key at https://app.kingminos.com after sign-in.
1115
+ The CLI never stores or logs a key.
1116
+
1117
+ API:
1118
+ Default ${DEFAULT_BASE_URL}
1119
+ Override with --base-url or ${BASE_URL_ENV}
1120
+
1121
+ Global flags:
1122
+ --token <key> Bearer token (overrides ${TOKEN_ENV})
1123
+ --base-url <url> API host
1124
+ --json Print the response body only (no Next: hints)
1125
+ --help, -h This help
1126
+
1127
+ Commands (live OpenAPI only):
1128
+ health GET /health
1129
+ openapi GET /openapi.yaml
1130
+ providers GET /v1/providers
1131
+ capabilities GET /v1/capabilities
1132
+ recipes GET /v1/recipes
1133
+ company resolve POST /v1/company/resolve
1134
+ company domain POST /v1/company/domain
1135
+ company hierarchy POST /v1/company/hierarchy
1136
+ company b2b-social POST /v1/company/b2b-social
1137
+ company icon POST /v1/company/icon
1138
+ company description POST /v1/company/description
1139
+ person verify-employment POST /v1/person/verify-employment
1140
+ person language POST /v1/person/language
1141
+ person phone POST /v1/person/phone
1142
+ person title POST /v1/person/title
1143
+ person name POST /v1/person/name
1144
+ person b2b-social POST /v1/person/b2b-social
1145
+ runs get <id> GET /v1/runs/{id}
1146
+ credentials put <provider> PUT /v1/credentials/{provider}
1147
+ credentials delete <provider> DELETE /v1/credentials/{provider}
1148
+ subjects delete <subject_key> DELETE /v1/subjects/{subject_key}
1149
+ batch <company|person> <cmd> POST each JSONL/JSON-array row (requires --file --priority)
1150
+
1151
+ Tiers:
1152
+ --priority speed|balance|accuracy|coverage maps to routing.path.
1153
+ Ask which path before a bulk run. Max yield = coverage.
1154
+ company.resolve value is the one-step lookup, not a tier.
1155
+
1156
+ Credits:
1157
+ Billing is for delivered answers. Print usage.credits.spent from the response.
1158
+ The CLI does not hardcode prices.
1159
+
1160
+ Your Keys providers: ${BYOK_PROVIDERS.join(", ")}
1161
+ Websearch is house-only (no credentials command).
1162
+ LeadMagic, Wiza, Findymail, and AI Ark are house-key vendors. ZoomInfo, BuiltWith,
1163
+ Prospeo, Apollo, Company URL Finder, People Data Labs, HG Insights, Brandfetch,
1164
+ and Enrich-CRM stay BYOK.
1165
+
1166
+ Exit codes: 0 ok \xB7 1 usage \xB7 2 unauthorized (401) \xB7 3 out of credits (402)
1167
+ 4 rate limited (429) \xB7 5 server (5xx) \xB7 6 other HTTP error
1168
+
1169
+ See kingminos <command> --help for request fields on that call.
1170
+ `;
1171
+ function commandHelp(command) {
1172
+ switch (command) {
1173
+ case "health":
1174
+ return `kingminos health
1175
+
1176
+ GET /health (no auth)
1177
+ `;
1178
+ case "openapi":
1179
+ return `kingminos openapi
1180
+
1181
+ GET /openapi.yaml (no auth)
1182
+ Prints the live OpenAPI document.
1183
+ `;
1184
+ case "providers":
1185
+ return `kingminos providers
1186
+
1187
+ GET /v1/providers
1188
+ Requires tenant Bearer (--token or ${TOKEN_ENV}).
1189
+ `;
1190
+ case "capabilities":
1191
+ return `kingminos capabilities
1192
+
1193
+ GET /v1/capabilities
1194
+ Requires tenant Bearer (--token or ${TOKEN_ENV}).
1195
+ `;
1196
+ case "recipes":
1197
+ return `kingminos recipes
1198
+
1199
+ GET /v1/recipes
1200
+ Requires tenant Bearer (--token or ${TOKEN_ENV}).
1201
+ Machine-readable chaining workflows. Ask speed / balance / accuracy / coverage before a bulk run. Max yield = coverage.
1202
+ `;
1203
+ case "batch":
1204
+ return `kingminos batch <company|person> <command> --file <path|-> --priority speed|balance|accuracy|coverage
1205
+
1206
+ Client-side loop over JSONL (or a JSON array) of request bodies. Not a bulk API.
1207
+ Requires tenant Bearer (--token or ${TOKEN_ENV}).
1208
+ Ask speed / balance / accuracy / coverage before a list run; --priority is required. Max yield = coverage.
1209
+ `;
1210
+ case "company.resolve":
1211
+ return `kingminos company resolve
1212
+
1213
+ POST /v1/company/resolve
1214
+ Requires tenant Bearer (--token or ${TOKEN_ENV}).
1215
+
1216
+ Body fields: --email --name --domain --website --dry-run --external-ref
1217
+ --path --preset --priority speed|balance|accuracy|coverage
1218
+ --routing <json> --compliance <json> --schema-version 2
1219
+ --body <json|@file|->
1220
+ Headers: --idempotency-key --explain default|full|minimal
1221
+ --schema 2 --max-provider-credits <n>
1222
+ `;
1223
+ case "company.domain":
1224
+ return `kingminos company domain
1225
+
1226
+ POST /v1/company/domain
1227
+ Requires tenant Bearer (--token or ${TOKEN_ENV}).
1228
+
1229
+ Body fields: --email --name --company-name --domain --website
1230
+ --city --state --country --postal-code --phone --linkedin-url
1231
+ --family-policy collapse_brand|separate_regional
1232
+ --path --preset --priority speed|balance|accuracy|coverage
1233
+ --skip-cache --dry-run --external-ref
1234
+ --routing <json> --compliance <json> --schema-version 2
1235
+ --body <json|@file|->
1236
+ Headers: --idempotency-key --explain default|full|minimal --schema 2
1237
+ --max-provider-credits <n> --cache bypass
1238
+ --cache-control no-cache|no-store
1239
+ `;
1240
+ case "company.b2b_social":
1241
+ return `kingminos company b2b-social
1242
+
1243
+ POST /v1/company/b2b-social
1244
+ Requires tenant Bearer (--token or ${TOKEN_ENV}).
1245
+
1246
+ Decide the company's B2B social page URL (result.linkedin_url).
1247
+ Pass --domain (required). --name is optional ranking only.
1248
+ --path speed is the bulk-list path (homepage-first, then ranked search).
1249
+ Delivered answers are billed; misses are not. See usage.credits.spent.
1250
+ Do not use company resolve for a B2B social page.
1251
+
1252
+ Body fields: --email --name --domain --website --linkedin-url
1253
+ --hint --country
1254
+ --path --preset --priority speed|balance|accuracy|coverage
1255
+ --dry-run --external-ref
1256
+ --routing <json> --compliance <json>
1257
+ --body <json|@file|->
1258
+ Headers: --idempotency-key --explain default|full|minimal
1259
+ --schema 2 --max-provider-credits <n>
1260
+ `;
1261
+ case "company.icon":
1262
+ return `kingminos company icon
1263
+
1264
+ POST /v1/company/icon
1265
+ Requires tenant Bearer (--token or ${TOKEN_ENV}).
1266
+
1267
+ Body fields: --email --name --domain --website
1268
+ --dry-run --external-ref
1269
+ --compliance <json>
1270
+ --body <json|@file|->
1271
+ Headers: --idempotency-key --explain default|full|minimal
1272
+ --schema 2
1273
+ `;
1274
+ case "company.description":
1275
+ return `kingminos company description
1276
+
1277
+ POST /v1/company/description
1278
+ Requires tenant Bearer (--token or ${TOKEN_ENV}).
1279
+
1280
+ Body fields: --email --name --domain --website
1281
+ --path --preset --priority speed|balance|accuracy|coverage
1282
+ --max-chars <80-500> --skip-cache --dry-run --external-ref
1283
+ --routing <json> --compliance <json>
1284
+ --body <json|@file|->
1285
+ Headers: --idempotency-key --explain default|full|minimal
1286
+ --schema 2 --max-provider-credits <n>
1287
+ --cache bypass --cache-control no-cache|no-store
1288
+ `;
1289
+ case "company.hierarchy":
1290
+ return `kingminos company hierarchy
1291
+
1292
+ POST /v1/company/hierarchy
1293
+ Requires tenant Bearer (--token or ${TOKEN_ENV}).
1294
+
1295
+ Body fields: --email --name --domain --website --country
1296
+ --path --preset --priority speed|balance|accuracy|coverage
1297
+ --dry-run --external-ref
1298
+ --routing <json> --compliance <json>
1299
+ --body <json|@file|->
1300
+ Headers: --idempotency-key --explain default|full|minimal
1301
+ --schema 2 --max-provider-credits <n>
1302
+ `;
1303
+ case "person.verify_employment":
1304
+ return `kingminos person verify-employment
1305
+
1306
+ POST /v1/person/verify-employment
1307
+ Requires tenant Bearer (--token or ${TOKEN_ENV}).
1308
+
1309
+ Body fields: --email --first-name --last-name --linkedin-url
1310
+ --expected-company <json>
1311
+ --expected-company-domain --expected-company-name
1312
+ --expected-company-website --priority speed|balance|accuracy|coverage
1313
+ --dry-run --external-ref --routing <json> --compliance <json>
1314
+ --body <json|@file|->
1315
+ Headers: --idempotency-key --explain default|full|minimal
1316
+ --schema 2 --max-provider-credits <n>
1317
+ `;
1318
+ case "person.language":
1319
+ return `kingminos person language
1320
+
1321
+ POST /v1/person/language
1322
+ Can we engage this person in English, and if not, in what language?
1323
+ Requires tenant Bearer (--token or ${TOKEN_ENV}).
1324
+
1325
+ Body fields: --email | --linkedin-url (one required)
1326
+ --first-name --last-name (match-only: rejects a mismatched profile)
1327
+ --priority speed|balance|accuracy --skip-cache
1328
+ --dry-run --external-ref --compliance <json>
1329
+ --body <json|@file|->
1330
+ Headers: --idempotency-key --explain default|full|minimal --schema 2
1331
+ `;
1332
+ case "person.phone":
1333
+ return `kingminos person phone
1334
+
1335
+ POST /v1/person/phone
1336
+ One dialable phone number for a person, plus the ranked rest.
1337
+ Requires tenant Bearer (--token or ${TOKEN_ENV}).
1338
+
1339
+ Body fields: --email | --linkedin-url (one required)
1340
+ --first-name --last-name (match-only: rejects a mismatched profile)
1341
+ --domain
1342
+ --phone-types mobile,landline,other,unknown (omit = any)
1343
+ --max-results <1-10> (default 3)
1344
+ --format e164|international|national|digits|digits_national
1345
+ (fills \`number\`; default e164; every phone also has \`formats\`)
1346
+ --priority speed|balance|accuracy|coverage
1347
+ --skip-cache --dry-run --external-ref --compliance <json>
1348
+ --body <json|@file|->
1349
+ Headers: --idempotency-key --explain default|full|minimal --schema 2
1350
+ --cache bypass --cache-control no-cache|no-store
1351
+ `;
1352
+ case "person.title":
1353
+ return `kingminos person title
1354
+
1355
+ POST /v1/person/title
1356
+ A person's current job title, seniority and function.
1357
+ Requires tenant Bearer (--token or ${TOKEN_ENV}).
1358
+
1359
+ Body fields: --email | --linkedin-url (one required)
1360
+ --first-name --last-name (match-only: rejects a mismatched profile)
1361
+ --company-domain (match-only current employer)
1362
+ --fields title,seniority,function (omit = all three)
1363
+ --path speed|balance|accuracy|coverage
1364
+ --priority speed|balance|accuracy|coverage
1365
+ --routing <json> (only / ignore)
1366
+ --skip-cache --dry-run --external-ref --compliance <json>
1367
+ --body <json|@file|->
1368
+ Headers: --idempotency-key --explain default|full|minimal --schema 2
1369
+ --cache bypass --cache-control no-cache|no-store
1370
+ Delivered answers are billed; misses are not. See usage.credits.spent.
1371
+ `;
1372
+ case "person.name":
1373
+ return `kingminos person name
1374
+
1375
+ POST /v1/person/name
1376
+ A person's proper-cased first, last and full name.
1377
+ Requires tenant Bearer (--token or ${TOKEN_ENV}).
1378
+
1379
+ Body fields: --email | --linkedin-url (one required)
1380
+ --path speed|balance|accuracy|coverage
1381
+ --priority speed|balance|accuracy|coverage
1382
+ --routing <json> (only / ignore)
1383
+ --skip-cache --dry-run --external-ref --compliance <json>
1384
+ --body <json|@file|->
1385
+ Headers: --idempotency-key --explain default|full|minimal --schema 2
1386
+ --cache bypass --cache-control no-cache|no-store
1387
+ Delivered answers are billed; misses are not. See usage.credits.spent.
1388
+ `;
1389
+ case "person.b2b_social":
1390
+ return `kingminos person b2b-social
1391
+
1392
+ POST /v1/person/b2b-social
1393
+ A person's B2B social profile URL (result.linkedin_url) from their email.
1394
+ Requires tenant Bearer (--token or ${TOKEN_ENV}).
1395
+
1396
+ Body fields: --email (required)
1397
+ --linkedin-url --first-name --last-name (match-only: rejects a mismatched profile)
1398
+ --path speed|balance|accuracy (no coverage)
1399
+ --priority speed|balance|accuracy
1400
+ --routing <json> (only / ignore)
1401
+ --skip-cache --dry-run --external-ref --compliance <json>
1402
+ --body <json|@file|->
1403
+ Headers: --idempotency-key --explain default|full|minimal --schema 2
1404
+ --cache bypass --cache-control no-cache|no-store
1405
+ Delivered answers are billed; misses are not. See usage.credits.spent.
1406
+ `;
1407
+ case "runs.get":
1408
+ return `kingminos runs get <id>
1409
+
1410
+ GET /v1/runs/{id}
1411
+ Requires tenant Bearer (--token or ${TOKEN_ENV}).
1412
+
1413
+ Headers: --explain default|full|minimal --schema 2
1414
+ `;
1415
+ case "credentials.put":
1416
+ return `kingminos credentials put <provider>
1417
+
1418
+ PUT /v1/credentials/{provider}
1419
+ Requires tenant Bearer (--token or ${TOKEN_ENV}).
1420
+ Providers: ${BYOK_PROVIDERS.join(", ")}
1421
+ Websearch is house-only (byok_not_supported).
1422
+
1423
+ zoominfo: --client-id --client-secret \u2192 { clientId, clientSecret }
1424
+ others: --api-key \u2192 { apiKey }
1425
+ or --body <json|@file|-> (api_key alias accepted by the API)
1426
+
1427
+ Responses never include the raw key. This CLI does not store or log it.
1428
+ `;
1429
+ case "credentials.delete":
1430
+ return `kingminos credentials delete <provider>
1431
+
1432
+ DELETE /v1/credentials/{provider}
1433
+ Requires tenant Bearer (--token or ${TOKEN_ENV}).
1434
+ Providers: ${BYOK_PROVIDERS.join(", ")}
1435
+ `;
1436
+ case "subjects.delete":
1437
+ return `kingminos subjects delete <subject_key>
1438
+
1439
+ DELETE /v1/subjects/{subject_key}
1440
+ Requires tenant Bearer with erase scope (--token or ${TOKEN_ENV}).
1441
+ Company: registrable domain. Person: email:<addr> or li:<slug>.
1442
+ `;
1443
+ default:
1444
+ return ROOT_HELP;
1445
+ }
1446
+ }
1447
+
1448
+ // src/http-error.ts
1449
+ function classifyHttpError(status, body) {
1450
+ const extra = errorSuffix(body);
1451
+ if (status === 401) {
1452
+ return {
1453
+ exitCode: EXIT.unauthorized,
1454
+ message: `Unauthorized (401)${authDetail(body)}. Set KINGMINOS_API_KEY or pass --token.${extra}`
1455
+ };
1456
+ }
1457
+ if (status === 402) {
1458
+ return {
1459
+ exitCode: EXIT.payment,
1460
+ message: `Out of credits (402). Add credits at https://app.kingminos.com.${extra}`
1461
+ };
1462
+ }
1463
+ if (status === 429) {
1464
+ const budget = isBudgetError(body);
1465
+ return {
1466
+ exitCode: EXIT.rateLimited,
1467
+ message: budget ? `Out of credits or daily key budget (429). Retry after Retry-After, or add credits.${extra}` : `Rate limited (429). Retry after the Retry-After header.${extra}`
1468
+ };
1469
+ }
1470
+ if (status >= 500) {
1471
+ return {
1472
+ exitCode: EXIT.server,
1473
+ message: `KingMinos server error (${status}). Retry the same Idempotency-Key if you sent one.${extra}`
1474
+ };
1475
+ }
1476
+ return {
1477
+ exitCode: EXIT.http,
1478
+ message: `HTTP ${status}${extra}`
1479
+ };
1480
+ }
1481
+ function authDetail(body) {
1482
+ const detail = stringField2(body, "detail");
1483
+ switch (detail) {
1484
+ case "missing":
1485
+ return ": missing Bearer token";
1486
+ case "malformed":
1487
+ return ": malformed Bearer token (send `Authorization: Bearer <key>`, not a bare key)";
1488
+ case "mismatch":
1489
+ return ": API key did not match";
1490
+ case void 0:
1491
+ return ": missing, malformed, or mismatched API key";
1492
+ default:
1493
+ return `: ${detail}`;
1494
+ }
1495
+ }
1496
+ function isBudgetError(body) {
1497
+ const error = stringField2(body, "error") ?? "";
1498
+ return error === "key_budget_exhausted" || error === "tenant_budget_exhausted" || error.includes("budget") || error.includes("credit");
1499
+ }
1500
+ function errorSuffix(body) {
1501
+ const error = stringField2(body, "error");
1502
+ const hint = stringField2(body, "hint");
1503
+ const parts = [error, hint].filter((part) => Boolean(part));
1504
+ return parts.length > 0 ? ` ${parts.join(" \u2014 ")}` : "";
1505
+ }
1506
+ function stringField2(body, key) {
1507
+ if (!body || typeof body !== "object" || Array.isArray(body)) {
1508
+ return void 0;
1509
+ }
1510
+ const value = body[key];
1511
+ return typeof value === "string" && value !== "" ? value : void 0;
1512
+ }
1513
+
1514
+ // src/kingminos.ts
1515
+ async function runCli(io) {
1516
+ try {
1517
+ return await runCliUnsafe(io);
1518
+ } catch (error) {
1519
+ const secrets = collectSecrets(io);
1520
+ if (error instanceof UsageError) {
1521
+ io.stderr(`${redact(error.message, secrets)}
1522
+ `);
1523
+ return EXIT.usage;
1524
+ }
1525
+ const message = error instanceof Error ? error.message : String(error);
1526
+ io.stderr(`${redact(message, secrets)}
1527
+ `);
1528
+ return EXIT.usage;
1529
+ }
1530
+ }
1531
+ async function runCliUnsafe(io) {
1532
+ const parsed = parseArgv(io.argv);
1533
+ if (parsed.flags.help === true || parsed.flags.h === true) {
1534
+ if (parsed.positionals.length === 0) {
1535
+ io.stdout(ROOT_HELP);
1536
+ return 0;
1537
+ }
1538
+ const command2 = resolveCommand(parsed.positionals);
1539
+ io.stdout(command2.name === "help" ? ROOT_HELP : commandHelp(command2.name));
1540
+ return 0;
1541
+ }
1542
+ const command = resolveCommand(parsed.positionals);
1543
+ if (command.name === "help") {
1544
+ io.stdout(command.topic ? commandHelp(helpTopic(command.topic)) : ROOT_HELP);
1545
+ return 0;
1546
+ }
1547
+ assertKnownFlags(command.name, parsed.flags);
1548
+ if (command.name === "batch") {
1549
+ return runBatch(io, command, parsed.flags);
1550
+ }
1551
+ const request = buildRequest(command, parsed.flags, {
1552
+ readFile: io.readFile ?? ((path) => readFileSync(path, "utf8")),
1553
+ stdin: io.stdin ?? ""
1554
+ });
1555
+ const token = request.auth ? requireToken(parsed.flags, io.env) : void 0;
1556
+ const baseUrl = resolveBaseUrl(parsed.flags, io.env);
1557
+ const response = await apiRequest({
1558
+ fetch: io.fetch,
1559
+ method: request.method,
1560
+ url: `${baseUrl}${request.path}`,
1561
+ token,
1562
+ headers: request.headers,
1563
+ body: request.body
1564
+ });
1565
+ const formatted = formatBody(response.text, response.contentType);
1566
+ const secrets = collectSecrets(io, parsed.flags);
1567
+ const asJson = flagBool(parsed.flags, "json") === true;
1568
+ if (response.status >= 400) {
1569
+ return writeHttpError(io, response.status, response.text, formatted, secrets, asJson);
1570
+ }
1571
+ io.stdout(redact(formatted, secrets));
1572
+ if (!asJson) {
1573
+ const parsedBody = parseJson(response.text);
1574
+ io.stdout(redact(formatDecisionNotes(parsedBody), secrets));
1575
+ io.stdout(redact(formatNextStepsHints(parsedBody), secrets));
1576
+ }
1577
+ return EXIT.ok;
1578
+ }
1579
+ function writeHttpError(io, status, rawText, formatted, secrets, asJson) {
1580
+ const classified = classifyHttpError(status, parseJson(rawText));
1581
+ if (asJson) {
1582
+ io.stderr(redact(formatted, secrets));
1583
+ } else {
1584
+ io.stderr(redact(`${classified.message}
1585
+ ${formatted}`, secrets));
1586
+ }
1587
+ return classified.exitCode;
1588
+ }
1589
+ function parseJson(text) {
1590
+ try {
1591
+ return JSON.parse(text);
1592
+ } catch {
1593
+ return null;
1594
+ }
1595
+ }
1596
+ async function runBatch(io, command, flags) {
1597
+ const rawPriority = flagString(flags, "priority");
1598
+ if (!rawPriority) {
1599
+ throw new UsageError(
1600
+ "Before a bulk run, ask which path: speed, balance, accuracy, or coverage. Re-run with --priority speed|balance|accuracy|coverage. Max yield = coverage."
1601
+ );
1602
+ }
1603
+ const priority = parsePriority(rawPriority);
1604
+ const file = flagString(flags, "file");
1605
+ if (!file) {
1606
+ throw new UsageError(
1607
+ "batch requires --file <path|-> (JSONL or a JSON array of request bodies)"
1608
+ );
1609
+ }
1610
+ const loader = {
1611
+ readFile: io.readFile ?? ((path) => readFileSync(path, "utf8")),
1612
+ stdin: io.stdin ?? ""
1613
+ };
1614
+ const raw = file === "-" ? loader.stdin : loader.readFile(file);
1615
+ const rows = parseBatchRows(raw);
1616
+ if (rows.length === 0) {
1617
+ throw new UsageError("batch file is empty");
1618
+ }
1619
+ const token = requireToken(flags, io.env);
1620
+ const baseUrl = resolveBaseUrl(flags, io.env);
1621
+ const secrets = collectSecrets(io, flags);
1622
+ const asJson = flagBool(flags, "json") === true;
1623
+ let worst = EXIT.ok;
1624
+ for (const [index, row] of rows.entries()) {
1625
+ const body = applyPriority(row, priority, command.capability);
1626
+ const request = buildRequest({ name: command.capability }, flags, {
1627
+ ...loader,
1628
+ stdin: JSON.stringify(body)
1629
+ });
1630
+ request.body = body;
1631
+ const response = await apiRequest({
1632
+ fetch: io.fetch,
1633
+ method: request.method,
1634
+ url: `${baseUrl}${request.path}`,
1635
+ token,
1636
+ headers: request.headers,
1637
+ body: request.body
1638
+ });
1639
+ const formatted = formatBody(response.text, response.contentType);
1640
+ if (response.status >= 400) {
1641
+ const prefix = asJson ? "" : `row ${index + 1} `;
1642
+ const code = writeHttpError(
1643
+ io,
1644
+ response.status,
1645
+ response.text,
1646
+ `${prefix}${formatted}`,
1647
+ secrets,
1648
+ asJson
1649
+ );
1650
+ worst = Math.max(worst, code);
1651
+ continue;
1652
+ }
1653
+ if (asJson) {
1654
+ io.stdout(redact(formatted, secrets));
1655
+ } else {
1656
+ io.stdout(redact(`# row ${index + 1}
1657
+ ${formatted}`, secrets));
1658
+ const parsedBody = parseJson(response.text);
1659
+ io.stdout(redact(formatDecisionNotes(parsedBody), secrets));
1660
+ io.stdout(redact(formatNextStepsHints(parsedBody), secrets));
1661
+ }
1662
+ }
1663
+ return worst;
1664
+ }
1665
+ function parseBatchRows(raw) {
1666
+ const trimmed = raw.trim();
1667
+ if (!trimmed) {
1668
+ return [];
1669
+ }
1670
+ if (trimmed.startsWith("[")) {
1671
+ const parsed = JSON.parse(trimmed);
1672
+ if (!Array.isArray(parsed)) {
1673
+ throw new UsageError("--file JSON array is invalid");
1674
+ }
1675
+ return parsed.map((row, index) => {
1676
+ if (!row || typeof row !== "object" || Array.isArray(row)) {
1677
+ throw new UsageError(`--file row ${index + 1} must be a JSON object`);
1678
+ }
1679
+ return row;
1680
+ });
1681
+ }
1682
+ return trimmed.split(/\r?\n/).flatMap((line, index) => {
1683
+ if (!line.trim()) {
1684
+ return [];
1685
+ }
1686
+ let parsed;
1687
+ try {
1688
+ parsed = JSON.parse(line);
1689
+ } catch {
1690
+ throw new UsageError(`--file line ${index + 1} is not valid JSON`);
1691
+ }
1692
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
1693
+ throw new UsageError(`--file line ${index + 1} must be a JSON object`);
1694
+ }
1695
+ return [parsed];
1696
+ });
1697
+ }
1698
+ function helpTopic(topic) {
1699
+ switch (topic) {
1700
+ case "resolve":
1701
+ return "company.resolve";
1702
+ case "domain":
1703
+ return "company.domain";
1704
+ case "hierarchy":
1705
+ return "company.hierarchy";
1706
+ case "b2b-social":
1707
+ return "company.b2b_social";
1708
+ case "icon":
1709
+ return "company.icon";
1710
+ case "description":
1711
+ return "company.description";
1712
+ case "verify-employment":
1713
+ return "person.verify_employment";
1714
+ case "language":
1715
+ return "person.language";
1716
+ case "phone":
1717
+ return "person.phone";
1718
+ case "title":
1719
+ return "person.title";
1720
+ case "name":
1721
+ return "person.name";
1722
+ case "recipes":
1723
+ return "recipes";
1724
+ case "batch":
1725
+ return "batch";
1726
+ default:
1727
+ return topic;
1728
+ }
1729
+ }
1730
+ function resolveBaseUrl(flags, env) {
1731
+ const fromFlag = typeof flags["base-url"] === "string" ? flags["base-url"] : void 0;
1732
+ const raw = fromFlag?.trim() || env[BASE_URL_ENV]?.trim() || DEFAULT_BASE_URL;
1733
+ return raw.replace(/\/+$/, "");
1734
+ }
1735
+ function optionalToken(flags, env) {
1736
+ const fromFlag = typeof flags.token === "string" ? flags.token.trim() : "";
1737
+ const fromEnv = env[TOKEN_ENV]?.trim() ?? "";
1738
+ return fromFlag || fromEnv || void 0;
1739
+ }
1740
+ function requireToken(flags, env) {
1741
+ const token = optionalToken(flags, env);
1742
+ if (!token) {
1743
+ throw new UsageError(`missing tenant Bearer: set ${TOKEN_ENV} or pass --token`);
1744
+ }
1745
+ return token;
1746
+ }
1747
+ function collectSecrets(io, flags) {
1748
+ const secrets = [];
1749
+ const push = (value) => {
1750
+ const trimmed = value?.trim();
1751
+ if (trimmed) {
1752
+ secrets.push(trimmed);
1753
+ }
1754
+ };
1755
+ push(io.env[TOKEN_ENV]);
1756
+ if (typeof flags?.token === "string") {
1757
+ push(flags.token);
1758
+ }
1759
+ if (typeof flags?.["api-key"] === "string") {
1760
+ push(flags["api-key"]);
1761
+ }
1762
+ if (typeof flags?.["client-secret"] === "string") {
1763
+ push(flags["client-secret"]);
1764
+ }
1765
+ return secrets;
1766
+ }
1767
+ function redact(text, secrets) {
1768
+ return secrets.reduce((out, secret) => out.split(secret).join("[redacted]"), text);
1769
+ }
1770
+ function launchedAsCli() {
1771
+ const self = fileURLToPath(import.meta.url);
1772
+ const argv1 = process.argv[1];
1773
+ if (!argv1) {
1774
+ return false;
1775
+ }
1776
+ try {
1777
+ return realpathSync(argv1) === realpathSync(self);
1778
+ } catch {
1779
+ return argv1.endsWith("kingminos.ts") || argv1.endsWith("kingminos.js") || /(^|\/)kingminos$/.test(argv1);
1780
+ }
1781
+ }
1782
+ async function readStdin() {
1783
+ if (process.stdin.isTTY) {
1784
+ return "";
1785
+ }
1786
+ const chunks = [];
1787
+ for await (const chunk of process.stdin) {
1788
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
1789
+ }
1790
+ return Buffer.concat(chunks).toString("utf8");
1791
+ }
1792
+ if (launchedAsCli()) {
1793
+ const readBodyStdin = process.argv.includes("--body") && process.argv.includes("-");
1794
+ const stdin = readBodyStdin ? await readStdin() : "";
1795
+ const code = await runCli({
1796
+ argv: process.argv.slice(2),
1797
+ env: process.env,
1798
+ stdout: (text) => {
1799
+ process.stdout.write(text);
1800
+ },
1801
+ stderr: (text) => {
1802
+ process.stderr.write(text);
1803
+ },
1804
+ fetch,
1805
+ stdin
1806
+ });
1807
+ process.exitCode = code;
1808
+ }
1809
+ export {
1810
+ BASE_URL_ENV,
1811
+ DEFAULT_BASE_URL,
1812
+ EXIT,
1813
+ ROOT_HELP,
1814
+ TOKEN_ENV,
1815
+ assertKnownFlags,
1816
+ buildRequest,
1817
+ classifyHttpError,
1818
+ formatDecisionNotes,
1819
+ parseArgv,
1820
+ resolveCommand,
1821
+ runCli,
1822
+ stripSafeToWrite
1823
+ };