@delegance/cli 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.
- package/LICENSE +21 -0
- package/README.md +178 -0
- package/dist/bin/delegance.d.ts +1 -0
- package/dist/bin/delegance.js +4314 -0
- package/dist/index.d.ts +300 -0
- package/dist/index.js +4373 -0
- package/package.json +70 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,4373 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
CLI_VERSION: () => CLI_VERSION,
|
|
34
|
+
clearConfig: () => clearConfig,
|
|
35
|
+
clearCredentials: () => clearCredentials,
|
|
36
|
+
createAppetiteCommand: () => createAppetiteCommand,
|
|
37
|
+
createAuthCommand: () => createAuthCommand,
|
|
38
|
+
createCOICommand: () => createCOICommand,
|
|
39
|
+
createChatCommand: () => createChatCommand,
|
|
40
|
+
createCompanyCommand: () => createCompanyCommand,
|
|
41
|
+
createConfigCommand: () => createConfigCommand,
|
|
42
|
+
createDocumentCommand: () => createDocumentCommand,
|
|
43
|
+
createKeysCommand: () => createKeysCommand,
|
|
44
|
+
createKnowledgeCommand: () => createKnowledgeCommand,
|
|
45
|
+
createLeadCommand: () => createLeadCommand,
|
|
46
|
+
createOnboardingCommand: () => createOnboardingCommand,
|
|
47
|
+
createPolicyCommand: () => createPolicyCommand,
|
|
48
|
+
createProgram: () => createProgram,
|
|
49
|
+
createQuoteCommand: () => createQuoteCommand,
|
|
50
|
+
createSOVCommand: () => createSOVCommand,
|
|
51
|
+
createUsageCommand: () => createUsageCommand,
|
|
52
|
+
getAllConfig: () => getAllConfig,
|
|
53
|
+
getApiKey: () => getApiKey,
|
|
54
|
+
getBaseUrl: () => getBaseUrl,
|
|
55
|
+
getConfig: () => getConfig,
|
|
56
|
+
getConfigPath: () => getConfigPath,
|
|
57
|
+
isAuthenticated: () => isAuthenticated,
|
|
58
|
+
maskApiKey: () => maskApiKey,
|
|
59
|
+
output: () => output_exports,
|
|
60
|
+
setConfig: () => setConfig,
|
|
61
|
+
storeApiKey: () => storeApiKey
|
|
62
|
+
});
|
|
63
|
+
module.exports = __toCommonJS(index_exports);
|
|
64
|
+
|
|
65
|
+
// src/commands/auth.ts
|
|
66
|
+
var os4 = __toESM(require("os"));
|
|
67
|
+
var import_commander2 = require("commander");
|
|
68
|
+
var import_inquirer2 = __toESM(require("inquirer"));
|
|
69
|
+
var import_ora2 = __toESM(require("ora"));
|
|
70
|
+
var import_sdk3 = require("@delegance/sdk");
|
|
71
|
+
|
|
72
|
+
// src/utils/config.ts
|
|
73
|
+
var import_conf = __toESM(require("conf"));
|
|
74
|
+
var fs = __toESM(require("fs"));
|
|
75
|
+
var path = __toESM(require("path"));
|
|
76
|
+
var os = __toESM(require("os"));
|
|
77
|
+
|
|
78
|
+
// src/version.ts
|
|
79
|
+
var CLI_VERSION = "0.2.0";
|
|
80
|
+
|
|
81
|
+
// src/utils/config.ts
|
|
82
|
+
var CONFIG_DIR = path.join(os.homedir(), ".delegance");
|
|
83
|
+
var CREDENTIALS_FILE = path.join(CONFIG_DIR, "credentials");
|
|
84
|
+
var API_KEY_REGEX = /^dlg_[a-zA-Z0-9]{8}_sk_[a-zA-Z0-9]{32}$/;
|
|
85
|
+
var DEFAULT_BASE_URL = "https://portal.delegancebrokerage.com";
|
|
86
|
+
var config = new import_conf.default({
|
|
87
|
+
projectName: "delegance",
|
|
88
|
+
projectVersion: CLI_VERSION,
|
|
89
|
+
// Owner-only, even though the key no longer lives here.
|
|
90
|
+
configFileMode: 384,
|
|
91
|
+
defaults: {
|
|
92
|
+
baseUrl: DEFAULT_BASE_URL,
|
|
93
|
+
outputFormat: "table"
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
function getConfig() {
|
|
97
|
+
return config;
|
|
98
|
+
}
|
|
99
|
+
function getConfigValue(key) {
|
|
100
|
+
return config.get(key);
|
|
101
|
+
}
|
|
102
|
+
function setConfig(key, value) {
|
|
103
|
+
config.set(key, value);
|
|
104
|
+
}
|
|
105
|
+
function getAllConfig() {
|
|
106
|
+
return config.store;
|
|
107
|
+
}
|
|
108
|
+
function clearConfig() {
|
|
109
|
+
config.clear();
|
|
110
|
+
}
|
|
111
|
+
function getConfigPath() {
|
|
112
|
+
return config.path;
|
|
113
|
+
}
|
|
114
|
+
function getCredentialsPath() {
|
|
115
|
+
return CREDENTIALS_FILE;
|
|
116
|
+
}
|
|
117
|
+
function writeCredentialsFile(apiKey) {
|
|
118
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 448 });
|
|
119
|
+
try {
|
|
120
|
+
fs.chmodSync(CONFIG_DIR, 448);
|
|
121
|
+
} catch {
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
if (fs.lstatSync(CREDENTIALS_FILE).isSymbolicLink()) {
|
|
125
|
+
throw new Error(`${CREDENTIALS_FILE} is a symlink; refusing to write the API key there`);
|
|
126
|
+
}
|
|
127
|
+
} catch (err) {
|
|
128
|
+
if (err.code !== "ENOENT") throw err;
|
|
129
|
+
}
|
|
130
|
+
const tmp = `${CREDENTIALS_FILE}.${process.pid}.${Date.now()}.tmp`;
|
|
131
|
+
const fd = fs.openSync(tmp, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL, 384);
|
|
132
|
+
try {
|
|
133
|
+
fs.writeSync(fd, apiKey);
|
|
134
|
+
} finally {
|
|
135
|
+
fs.closeSync(fd);
|
|
136
|
+
}
|
|
137
|
+
fs.renameSync(tmp, CREDENTIALS_FILE);
|
|
138
|
+
}
|
|
139
|
+
function readCredentialsFile() {
|
|
140
|
+
try {
|
|
141
|
+
const key = fs.readFileSync(CREDENTIALS_FILE, "utf-8").trim();
|
|
142
|
+
return key || void 0;
|
|
143
|
+
} catch {
|
|
144
|
+
return void 0;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function storeApiKey(apiKey, meta = {}) {
|
|
148
|
+
writeCredentialsFile(apiKey);
|
|
149
|
+
config.delete("apiKey");
|
|
150
|
+
config.set("apiKeyHint", maskApiKey(apiKey));
|
|
151
|
+
if (meta.expiresAt) config.set("apiKeyExpiresAt", meta.expiresAt);
|
|
152
|
+
else config.delete("apiKeyExpiresAt");
|
|
153
|
+
}
|
|
154
|
+
function setStoredKeyExpiry(expiresAt) {
|
|
155
|
+
if (expiresAt) config.set("apiKeyExpiresAt", expiresAt);
|
|
156
|
+
else config.delete("apiKeyExpiresAt");
|
|
157
|
+
}
|
|
158
|
+
function migrateLegacyConfigKey(log = (l) => process.stderr.write(`${l}
|
|
159
|
+
`)) {
|
|
160
|
+
try {
|
|
161
|
+
if (fs.existsSync(CREDENTIALS_FILE) && (fs.statSync(CREDENTIALS_FILE).mode & 63) !== 0) {
|
|
162
|
+
fs.chmodSync(CREDENTIALS_FILE, 384);
|
|
163
|
+
}
|
|
164
|
+
} catch {
|
|
165
|
+
}
|
|
166
|
+
const legacy = config.get("apiKey");
|
|
167
|
+
if (typeof legacy !== "string" || !legacy) return "none";
|
|
168
|
+
if (!API_KEY_REGEX.test(legacy.trim())) {
|
|
169
|
+
config.delete("apiKey");
|
|
170
|
+
return "none";
|
|
171
|
+
}
|
|
172
|
+
const existing = readCredentialsFile();
|
|
173
|
+
if (existing) {
|
|
174
|
+
config.delete("apiKey");
|
|
175
|
+
config.set("apiKeyHint", maskApiKey(existing));
|
|
176
|
+
log(`Removed a plaintext copy of your API key from ${config.path}; the key in ${CREDENTIALS_FILE} is unchanged.`);
|
|
177
|
+
return "dropped_duplicate";
|
|
178
|
+
}
|
|
179
|
+
try {
|
|
180
|
+
writeCredentialsFile(legacy.trim());
|
|
181
|
+
} catch (err) {
|
|
182
|
+
log(`Your API key is still stored in ${config.path}. Could not move it to ${CREDENTIALS_FILE}: ${err.message}`);
|
|
183
|
+
return "failed";
|
|
184
|
+
}
|
|
185
|
+
config.delete("apiKey");
|
|
186
|
+
config.set("apiKeyHint", maskApiKey(legacy.trim()));
|
|
187
|
+
log(`Moved your API key from ${config.path} into ${CREDENTIALS_FILE} (readable only by you).`);
|
|
188
|
+
return "moved";
|
|
189
|
+
}
|
|
190
|
+
function getApiKey() {
|
|
191
|
+
if (process.env.DELEGANCE_API_KEY) {
|
|
192
|
+
return process.env.DELEGANCE_API_KEY;
|
|
193
|
+
}
|
|
194
|
+
const stored = readCredentialsFile();
|
|
195
|
+
if (stored) return stored;
|
|
196
|
+
const legacy = config.get("apiKey");
|
|
197
|
+
return typeof legacy === "string" && API_KEY_REGEX.test(legacy.trim()) ? legacy.trim() : void 0;
|
|
198
|
+
}
|
|
199
|
+
function isAuthenticated() {
|
|
200
|
+
return !!getApiKey();
|
|
201
|
+
}
|
|
202
|
+
function clearCredentials() {
|
|
203
|
+
config.delete("apiKey");
|
|
204
|
+
config.delete("apiKeyHint");
|
|
205
|
+
config.delete("apiKeyExpiresAt");
|
|
206
|
+
if (fs.existsSync(CREDENTIALS_FILE)) {
|
|
207
|
+
fs.unlinkSync(CREDENTIALS_FILE);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
function getBaseUrl() {
|
|
211
|
+
return process.env.DELEGANCE_BASE_URL || config.get("baseUrl") || DEFAULT_BASE_URL;
|
|
212
|
+
}
|
|
213
|
+
function maskApiKey(apiKey) {
|
|
214
|
+
if (!apiKey || apiKey.length < 20) {
|
|
215
|
+
return "***";
|
|
216
|
+
}
|
|
217
|
+
const prefix = apiKey.substring(0, 12);
|
|
218
|
+
const suffix = apiKey.substring(apiKey.length - 4);
|
|
219
|
+
return `${prefix}...${suffix}`;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// src/utils/client.ts
|
|
223
|
+
var import_sdk = require("@delegance/sdk");
|
|
224
|
+
|
|
225
|
+
// src/utils/output.ts
|
|
226
|
+
var output_exports = {};
|
|
227
|
+
__export(output_exports, {
|
|
228
|
+
debug: () => debug,
|
|
229
|
+
error: () => error,
|
|
230
|
+
formatCurrency: () => formatCurrency,
|
|
231
|
+
formatDate: () => formatDate,
|
|
232
|
+
formatFileSize: () => formatFileSize,
|
|
233
|
+
formatStatus: () => formatStatus,
|
|
234
|
+
header: () => header,
|
|
235
|
+
info: () => info,
|
|
236
|
+
json: () => json,
|
|
237
|
+
keyValue: () => keyValue,
|
|
238
|
+
output: () => output,
|
|
239
|
+
success: () => success,
|
|
240
|
+
table: () => table,
|
|
241
|
+
warn: () => warn
|
|
242
|
+
});
|
|
243
|
+
var import_chalk = __toESM(require("chalk"));
|
|
244
|
+
var import_cli_table3 = __toESM(require("cli-table3"));
|
|
245
|
+
var import_figures = __toESM(require("figures"));
|
|
246
|
+
function success(message) {
|
|
247
|
+
console.log(import_chalk.default.green(`${import_figures.default.tick} ${message}`));
|
|
248
|
+
}
|
|
249
|
+
function error(message, details) {
|
|
250
|
+
console.error(import_chalk.default.red(`${import_figures.default.cross} ${message}`));
|
|
251
|
+
if (details) {
|
|
252
|
+
console.error(import_chalk.default.gray(` ${details}`));
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
function warn(message) {
|
|
256
|
+
console.log(import_chalk.default.yellow(`${import_figures.default.warning} ${message}`));
|
|
257
|
+
}
|
|
258
|
+
function info(message) {
|
|
259
|
+
console.log(import_chalk.default.blue(`${import_figures.default.info} ${message}`));
|
|
260
|
+
}
|
|
261
|
+
function debug(message) {
|
|
262
|
+
if (process.env.DEBUG || process.env.VERBOSE) {
|
|
263
|
+
console.log(import_chalk.default.gray(`${import_figures.default.pointer} ${message}`));
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
function header(text) {
|
|
267
|
+
console.log();
|
|
268
|
+
console.log(import_chalk.default.bold.cyan(text));
|
|
269
|
+
console.log(import_chalk.default.gray("\u2500".repeat(text.length)));
|
|
270
|
+
}
|
|
271
|
+
function keyValue(keyOrData, value) {
|
|
272
|
+
if (typeof keyOrData === "string") {
|
|
273
|
+
console.log(` ${import_chalk.default.gray(keyOrData.padEnd(15))} ${formatValue(value)}`);
|
|
274
|
+
} else {
|
|
275
|
+
const data = keyOrData;
|
|
276
|
+
const maxKeyLength = Math.max(...Object.keys(data).map((k) => k.length));
|
|
277
|
+
for (const [key, val] of Object.entries(data)) {
|
|
278
|
+
const paddedKey = key.padEnd(maxKeyLength);
|
|
279
|
+
console.log(` ${import_chalk.default.gray(paddedKey)} ${formatValue(val)}`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
function formatValue(value) {
|
|
284
|
+
if (value === null || value === void 0) {
|
|
285
|
+
return import_chalk.default.gray("(none)");
|
|
286
|
+
}
|
|
287
|
+
if (typeof value === "boolean") {
|
|
288
|
+
return value ? import_chalk.default.green("yes") : import_chalk.default.red("no");
|
|
289
|
+
}
|
|
290
|
+
if (typeof value === "number") {
|
|
291
|
+
return import_chalk.default.yellow(String(value));
|
|
292
|
+
}
|
|
293
|
+
if (value instanceof Date) {
|
|
294
|
+
return import_chalk.default.cyan(value.toISOString());
|
|
295
|
+
}
|
|
296
|
+
if (Array.isArray(value)) {
|
|
297
|
+
return import_chalk.default.cyan(value.join(", "));
|
|
298
|
+
}
|
|
299
|
+
return String(value);
|
|
300
|
+
}
|
|
301
|
+
function table(headers, rows, options = {}) {
|
|
302
|
+
const tbl = new import_cli_table3.default({
|
|
303
|
+
head: headers.map((h) => import_chalk.default.bold(h)),
|
|
304
|
+
style: {
|
|
305
|
+
head: ["cyan"],
|
|
306
|
+
compact: options.compact
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
for (const row of rows) {
|
|
310
|
+
tbl.push(row.map((cell) => formatValue(cell)));
|
|
311
|
+
}
|
|
312
|
+
console.log(tbl.toString());
|
|
313
|
+
}
|
|
314
|
+
function json(data) {
|
|
315
|
+
console.log(JSON.stringify(data, null, 2));
|
|
316
|
+
}
|
|
317
|
+
function output(data, format = "table") {
|
|
318
|
+
if (format === "json") {
|
|
319
|
+
json(data);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
if (Array.isArray(data) && data.length > 0) {
|
|
323
|
+
const headers = Object.keys(data[0]);
|
|
324
|
+
const rows = data.map((item) => headers.map((h) => item[h]));
|
|
325
|
+
table(headers, rows);
|
|
326
|
+
} else if (typeof data === "object" && data !== null) {
|
|
327
|
+
keyValue(data);
|
|
328
|
+
} else {
|
|
329
|
+
console.log(data);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
function formatDate(dateStr) {
|
|
333
|
+
if (!dateStr) return import_chalk.default.gray("(none)");
|
|
334
|
+
const date = new Date(dateStr);
|
|
335
|
+
return date.toLocaleDateString("en-US", {
|
|
336
|
+
year: "numeric",
|
|
337
|
+
month: "short",
|
|
338
|
+
day: "numeric",
|
|
339
|
+
hour: "2-digit",
|
|
340
|
+
minute: "2-digit"
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
function formatFileSize(bytes) {
|
|
344
|
+
if (bytes === void 0) return import_chalk.default.gray("(unknown)");
|
|
345
|
+
const units = ["B", "KB", "MB", "GB"];
|
|
346
|
+
let size = bytes;
|
|
347
|
+
let unitIndex = 0;
|
|
348
|
+
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
349
|
+
size /= 1024;
|
|
350
|
+
unitIndex++;
|
|
351
|
+
}
|
|
352
|
+
return `${size.toFixed(1)} ${units[unitIndex]}`;
|
|
353
|
+
}
|
|
354
|
+
function formatStatus(status) {
|
|
355
|
+
switch (status) {
|
|
356
|
+
case "completed":
|
|
357
|
+
case "processed":
|
|
358
|
+
return import_chalk.default.green(status);
|
|
359
|
+
case "pending":
|
|
360
|
+
return import_chalk.default.yellow(status);
|
|
361
|
+
case "processing":
|
|
362
|
+
return import_chalk.default.blue(status);
|
|
363
|
+
case "error":
|
|
364
|
+
case "failed":
|
|
365
|
+
return import_chalk.default.red(status);
|
|
366
|
+
default:
|
|
367
|
+
return status;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
function formatCurrency(amount) {
|
|
371
|
+
if (amount === void 0 || amount === null) return import_chalk.default.gray("(none)");
|
|
372
|
+
return new Intl.NumberFormat("en-US", {
|
|
373
|
+
style: "currency",
|
|
374
|
+
currency: "USD",
|
|
375
|
+
minimumFractionDigits: 0,
|
|
376
|
+
maximumFractionDigits: 0
|
|
377
|
+
}).format(amount);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// src/utils/last-command.ts
|
|
381
|
+
var fs2 = __toESM(require("fs"));
|
|
382
|
+
var os2 = __toESM(require("os"));
|
|
383
|
+
var path2 = __toESM(require("path"));
|
|
384
|
+
var COMMAND_RE = /^[a-z][a-z0-9-]*(?: [a-z][a-z0-9-]*){0,3}$/;
|
|
385
|
+
var TRACE_RE = /^[0-9a-fA-F-]{8,64}$/;
|
|
386
|
+
var ISO_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
387
|
+
function lastCommandPath() {
|
|
388
|
+
return path2.join(os2.homedir(), ".delegance", "last-command.json");
|
|
389
|
+
}
|
|
390
|
+
function sanitizeLastCommand(raw) {
|
|
391
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
392
|
+
const r = raw;
|
|
393
|
+
if (typeof r.command !== "string" || r.command.length > 60 || !COMMAND_RE.test(r.command)) return null;
|
|
394
|
+
if (typeof r.exitCode !== "number" || !Number.isInteger(r.exitCode) || r.exitCode < -1 || r.exitCode > 255) return null;
|
|
395
|
+
if (typeof r.at !== "string" || !ISO_RE.test(r.at)) return null;
|
|
396
|
+
const out = { command: r.command, exitCode: r.exitCode, at: r.at };
|
|
397
|
+
if (typeof r.traceId === "string" && TRACE_RE.test(r.traceId)) out.traceId = r.traceId;
|
|
398
|
+
return out;
|
|
399
|
+
}
|
|
400
|
+
function readLastCommand() {
|
|
401
|
+
try {
|
|
402
|
+
return sanitizeLastCommand(JSON.parse(fs2.readFileSync(lastCommandPath(), "utf8")));
|
|
403
|
+
} catch {
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
var current = null;
|
|
408
|
+
function noteTraceId(traceId) {
|
|
409
|
+
if (current && typeof traceId === "string" && TRACE_RE.test(traceId)) current.traceId = traceId;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// src/utils/client.ts
|
|
413
|
+
var PORTAL_URL = "https://portal.delegancebrokerage.com";
|
|
414
|
+
function getClient() {
|
|
415
|
+
const apiKey = getApiKey();
|
|
416
|
+
if (!apiKey) {
|
|
417
|
+
error("Not authenticated");
|
|
418
|
+
info('New to Delegance? Run "delegance signup" to create an account.');
|
|
419
|
+
info('Have an API key? Run "delegance auth login", or set DELEGANCE_API_KEY.');
|
|
420
|
+
process.exit(1);
|
|
421
|
+
}
|
|
422
|
+
return new import_sdk.DeleganceClient({
|
|
423
|
+
apiKey,
|
|
424
|
+
baseUrl: getBaseUrl()
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
function getCompanyId(options) {
|
|
428
|
+
const companyId = options.company || getConfigValue("defaultCompanyId");
|
|
429
|
+
if (!companyId) {
|
|
430
|
+
error("No company specified");
|
|
431
|
+
info('Use --company <id> or set a default with "delegance company use <id>"');
|
|
432
|
+
process.exit(1);
|
|
433
|
+
}
|
|
434
|
+
return companyId;
|
|
435
|
+
}
|
|
436
|
+
function describeApiError(err) {
|
|
437
|
+
const traceSuffix = err instanceof import_sdk.DeleganceError && err.traceId ? ` (trace ${err.traceId})` : "";
|
|
438
|
+
if (err instanceof import_sdk.NotImplementedError) {
|
|
439
|
+
return { message: `${err.message}${traceSuffix}`, hint: `This is not available over the API yet. Use the portal: ${PORTAL_URL}` };
|
|
440
|
+
}
|
|
441
|
+
if (err instanceof import_sdk.AuthenticationError) {
|
|
442
|
+
if (err.reason === "key_expired" || err.reason === "key_membership_revoked") {
|
|
443
|
+
return { message: `${err.message}${traceSuffix}`, hint: 'Run "delegance auth login" to sign in again.' };
|
|
444
|
+
}
|
|
445
|
+
return { message: `${err.message}${traceSuffix}`, hint: 'Check your key with "delegance auth status", or log in again.' };
|
|
446
|
+
}
|
|
447
|
+
if (err instanceof import_sdk.ForbiddenError) {
|
|
448
|
+
return { message: `${err.message}${traceSuffix}`, hint: "Your API key lacks the scope for this command. Create a key with the right scope in the portal." };
|
|
449
|
+
}
|
|
450
|
+
if (err instanceof import_sdk.RateLimitError) {
|
|
451
|
+
const wait = err.retryAfter ? ` Retry in ${err.retryAfter}s.` : "";
|
|
452
|
+
return { message: `${err.message}${traceSuffix}`, hint: `Rate limited.${wait}` };
|
|
453
|
+
}
|
|
454
|
+
if (err instanceof import_sdk.NotFoundError) {
|
|
455
|
+
return { message: `${err.message}${traceSuffix}` };
|
|
456
|
+
}
|
|
457
|
+
if (err instanceof Error) {
|
|
458
|
+
return { message: `${err.message}${traceSuffix}` };
|
|
459
|
+
}
|
|
460
|
+
return { message: String(err) };
|
|
461
|
+
}
|
|
462
|
+
function failWith(label, err) {
|
|
463
|
+
if (err instanceof import_sdk.DeleganceError) noteTraceId(err.traceId);
|
|
464
|
+
const { message, hint } = describeApiError(err);
|
|
465
|
+
error(label, message);
|
|
466
|
+
if (hint) info(hint);
|
|
467
|
+
process.exit(1);
|
|
468
|
+
}
|
|
469
|
+
function positiveInt(value, flag) {
|
|
470
|
+
const n = Number(value);
|
|
471
|
+
if (!Number.isInteger(n) || n <= 0) {
|
|
472
|
+
error(`${flag} must be a positive whole number, got "${value}"`);
|
|
473
|
+
process.exit(1);
|
|
474
|
+
}
|
|
475
|
+
return n;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// src/utils/browser.ts
|
|
479
|
+
var import_child_process = require("child_process");
|
|
480
|
+
function openerFor(platform) {
|
|
481
|
+
if (platform === "darwin") return { cmd: "open", args: [] };
|
|
482
|
+
if (platform === "win32") return { cmd: "rundll32", args: ["url.dll,FileProtocolHandler"] };
|
|
483
|
+
return { cmd: "xdg-open", args: [] };
|
|
484
|
+
}
|
|
485
|
+
function openInBrowser(url, platform = process.platform) {
|
|
486
|
+
let parsed;
|
|
487
|
+
try {
|
|
488
|
+
parsed = new URL(url);
|
|
489
|
+
} catch {
|
|
490
|
+
return false;
|
|
491
|
+
}
|
|
492
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return false;
|
|
493
|
+
const { cmd, args } = openerFor(platform);
|
|
494
|
+
try {
|
|
495
|
+
const child = (0, import_child_process.spawn)(cmd, [...args, parsed.toString()], { stdio: "ignore", detached: true });
|
|
496
|
+
child.on("error", () => void 0);
|
|
497
|
+
child.unref();
|
|
498
|
+
return true;
|
|
499
|
+
} catch {
|
|
500
|
+
return false;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// src/commands/signup.ts
|
|
505
|
+
var import_crypto = require("crypto");
|
|
506
|
+
var import_commander = require("commander");
|
|
507
|
+
var import_inquirer = __toESM(require("inquirer"));
|
|
508
|
+
var import_ora = __toESM(require("ora"));
|
|
509
|
+
var import_sdk2 = require("@delegance/sdk");
|
|
510
|
+
|
|
511
|
+
// src/utils/pending-signup.ts
|
|
512
|
+
var fs3 = __toESM(require("fs"));
|
|
513
|
+
var path3 = __toESM(require("path"));
|
|
514
|
+
var os3 = __toESM(require("os"));
|
|
515
|
+
function pendingSignupPath(home = os3.homedir()) {
|
|
516
|
+
return path3.join(home, ".delegance", "pending-signup.json");
|
|
517
|
+
}
|
|
518
|
+
function savePendingSignup(p, home) {
|
|
519
|
+
const file = pendingSignupPath(home);
|
|
520
|
+
fs3.mkdirSync(path3.dirname(file), { recursive: true, mode: 448 });
|
|
521
|
+
try {
|
|
522
|
+
if (fs3.lstatSync(file).isSymbolicLink()) throw new Error(`${file} is a symlink; refusing to write the signup secret there`);
|
|
523
|
+
} catch (err) {
|
|
524
|
+
if (err.code !== "ENOENT") throw err;
|
|
525
|
+
}
|
|
526
|
+
const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
527
|
+
const fd = fs3.openSync(tmp, fs3.constants.O_WRONLY | fs3.constants.O_CREAT | fs3.constants.O_EXCL, 384);
|
|
528
|
+
try {
|
|
529
|
+
fs3.writeSync(fd, JSON.stringify(p, null, 2));
|
|
530
|
+
} finally {
|
|
531
|
+
fs3.closeSync(fd);
|
|
532
|
+
}
|
|
533
|
+
fs3.renameSync(tmp, file);
|
|
534
|
+
}
|
|
535
|
+
function loadPendingSignup(home) {
|
|
536
|
+
try {
|
|
537
|
+
const parsed = JSON.parse(fs3.readFileSync(pendingSignupPath(home), "utf8"));
|
|
538
|
+
return parsed && typeof parsed.enrollmentId === "string" && typeof parsed.deviceSecret === "string" ? parsed : null;
|
|
539
|
+
} catch {
|
|
540
|
+
return null;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
function clearPendingSignup(home) {
|
|
544
|
+
try {
|
|
545
|
+
fs3.unlinkSync(pendingSignupPath(home));
|
|
546
|
+
} catch {
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// src/commands/signup.ts
|
|
551
|
+
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
552
|
+
var COMPANY_SIZES = ["0", "1", "2-10", "11-25", "26-50", "51-100", "100-499", "500+"];
|
|
553
|
+
var REQUIRED_FLAGS = [
|
|
554
|
+
["email", "--email"],
|
|
555
|
+
["business", "--business"],
|
|
556
|
+
["first", "--first"],
|
|
557
|
+
["last", "--last"],
|
|
558
|
+
["industry", "--industry"],
|
|
559
|
+
["size", "--size"],
|
|
560
|
+
["street", "--street"],
|
|
561
|
+
["city", "--city"],
|
|
562
|
+
["state", "--state"],
|
|
563
|
+
["zip", "--zip"]
|
|
564
|
+
];
|
|
565
|
+
function missingSignupFields(options) {
|
|
566
|
+
return REQUIRED_FLAGS.filter(([k]) => !String(options[k] ?? "").trim()).map(([, flag]) => flag);
|
|
567
|
+
}
|
|
568
|
+
function addressProblem(a) {
|
|
569
|
+
if (!a.street.trim() || !a.city.trim()) return "Street and city are required.";
|
|
570
|
+
if (!/^[A-Za-z]{2}$/.test(a.state.trim())) return "State must be the 2-letter code, e.g. TX.";
|
|
571
|
+
if (!/^\d{5}(-\d{4})?$/.test(a.zip.trim())) return "ZIP must be 5 digits (or ZIP+4).";
|
|
572
|
+
return null;
|
|
573
|
+
}
|
|
574
|
+
function exchangeRefusal(err) {
|
|
575
|
+
if (!(err instanceof import_sdk2.DeleganceError)) return null;
|
|
576
|
+
switch (err.reason) {
|
|
577
|
+
case "account_exists":
|
|
578
|
+
return `This email already has a Delegance account, so signup did not attach it to a new one. Sign in and create a key at ${PORTAL_URL}/portal/settings/api-keys`;
|
|
579
|
+
case "already_collected":
|
|
580
|
+
return `The key for this signup was already collected. If you lost it, create a new key at ${PORTAL_URL}/portal/settings/api-keys`;
|
|
581
|
+
case "expired":
|
|
582
|
+
return 'This signup expired (24 hours). Run "delegance signup" again.';
|
|
583
|
+
default:
|
|
584
|
+
return null;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
async function ensureEnrolled(pending) {
|
|
588
|
+
if (pending.enrollmentId) return pending;
|
|
589
|
+
const signup = (0, import_sdk2.createSignup)({ baseUrl: pending.baseUrl });
|
|
590
|
+
const hash = (0, import_crypto.createHash)("sha256").update(pending.deviceSecret).digest("hex");
|
|
591
|
+
const enrollment = await signup.enroll(pending.bootstrapKey, {
|
|
592
|
+
deviceSecretHash: hash,
|
|
593
|
+
firstName: pending.firstName ?? "",
|
|
594
|
+
lastName: pending.lastName ?? ""
|
|
595
|
+
});
|
|
596
|
+
const next = { ...pending, enrollmentId: enrollment.enrollmentId, verificationSent: false };
|
|
597
|
+
savePendingSignup(next);
|
|
598
|
+
return sendVerification(next);
|
|
599
|
+
}
|
|
600
|
+
async function sendVerification(pending) {
|
|
601
|
+
await (0, import_sdk2.createSignup)({ baseUrl: pending.baseUrl }).requestVerification(pending.bootstrapKey);
|
|
602
|
+
const next = { ...pending, verificationSent: true };
|
|
603
|
+
savePendingSignup(next);
|
|
604
|
+
return next;
|
|
605
|
+
}
|
|
606
|
+
function storeOrPrintKey(apiKey, store = storeApiKey) {
|
|
607
|
+
try {
|
|
608
|
+
store(apiKey);
|
|
609
|
+
return true;
|
|
610
|
+
} catch (err) {
|
|
611
|
+
error(`Could not save the key to ~/.delegance/credentials: ${err.message}`);
|
|
612
|
+
warn('Your key is below. It is shown once. Save it now, then run "delegance auth login --key <key>".');
|
|
613
|
+
console.log(apiKey);
|
|
614
|
+
return false;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
async function waitAndStore(pending, timeoutMs) {
|
|
618
|
+
const signup = (0, import_sdk2.createSignup)({ baseUrl: pending.baseUrl });
|
|
619
|
+
const spinner = (0, import_ora.default)(`Waiting for you to click the link sent to ${pending.email} (Ctrl-C is safe: resume with "delegance verify")`).start();
|
|
620
|
+
const started = Date.now();
|
|
621
|
+
try {
|
|
622
|
+
const result = await signup.waitForKey(pending.enrollmentId, pending.deviceSecret, {
|
|
623
|
+
timeoutMs,
|
|
624
|
+
intervalMs: 5e3,
|
|
625
|
+
onPoll: () => {
|
|
626
|
+
spinner.text = `Waiting for the email confirmation (${Math.round((Date.now() - started) / 1e3)}s). Check ${pending.email}.`;
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
if (result.status !== "ready") {
|
|
630
|
+
spinner.warn("Still waiting on the email confirmation.");
|
|
631
|
+
info('Run "delegance verify" after you click the link.');
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
spinner.stop();
|
|
635
|
+
const stored = storeOrPrintKey(result.apiKey);
|
|
636
|
+
setConfig("defaultCompanyId", result.companyId);
|
|
637
|
+
clearPendingSignup();
|
|
638
|
+
if (stored) success(`Verified. API key ${maskApiKey(result.apiKey)} saved to ~/.delegance/credentials`);
|
|
639
|
+
success("You can quote now: delegance quote start --coverage gl");
|
|
640
|
+
} catch (err) {
|
|
641
|
+
spinner.stop();
|
|
642
|
+
const why = exchangeRefusal(err);
|
|
643
|
+
if (why) {
|
|
644
|
+
if (err.reason !== "already_collected") clearPendingSignup();
|
|
645
|
+
error(why);
|
|
646
|
+
process.exit(1);
|
|
647
|
+
}
|
|
648
|
+
failWith("Could not collect the key", err);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
function signupPrompts(options, interactive) {
|
|
652
|
+
if (!interactive) return [];
|
|
653
|
+
const all = [
|
|
654
|
+
{ type: "input", name: "email", message: "Work email:", when: !options.email, validate: (v) => EMAIL_RE.test(v) || "Enter an email address" },
|
|
655
|
+
{ type: "input", name: "business", message: "Business name:", when: !options.business, validate: (v) => !!v.trim() || "Required" },
|
|
656
|
+
{ type: "input", name: "first", message: "Your first name:", when: !options.first, validate: (v) => !!v.trim() || "Required" },
|
|
657
|
+
{ type: "input", name: "last", message: "Your last name:", when: !options.last, validate: (v) => !!v.trim() || "Required" },
|
|
658
|
+
{ type: "input", name: "industry", message: "What does the business do?", when: !options.industry, validate: (v) => !!v.trim() || "Required" },
|
|
659
|
+
{ type: "list", name: "size", message: "Employees:", choices: COMPANY_SIZES, when: !options.size },
|
|
660
|
+
{ type: "input", name: "street", message: "Business street address:", when: !options.street, validate: (v) => !!v.trim() || "Required" },
|
|
661
|
+
{ type: "input", name: "city", message: "City:", when: !options.city, validate: (v) => !!v.trim() || "Required" },
|
|
662
|
+
{ type: "input", name: "state", message: "State (2 letters):", when: !options.state, validate: (v) => /^[A-Za-z]{2}$/.test(v.trim()) || "Use the 2-letter code, e.g. TX" },
|
|
663
|
+
{ type: "input", name: "zip", message: "ZIP:", when: !options.zip, validate: (v) => /^\d{5}(-\d{4})?$/.test(v.trim()) || "5 digits" },
|
|
664
|
+
{ type: "input", name: "description", message: "In a sentence, what does the business do? (optional)", when: !options.description }
|
|
665
|
+
];
|
|
666
|
+
return all.filter((q) => q.when !== false).map(({ when: _when, ...q }) => q);
|
|
667
|
+
}
|
|
668
|
+
function createSignupCommand() {
|
|
669
|
+
return new import_commander.Command("signup").description("Create a Delegance account and get an API key, from the terminal").option("-e, --email <email>", "Your work email (the confirm link goes here)").option("-b, --business <name>", "Business name").option("--first <name>", "Your first name").option("--last <name>", "Your last name").option("-i, --industry <industry>", 'What the business does, e.g. "commercial bakery"').option("--size <size>", `Employees: ${COMPANY_SIZES.join(", ")}`).option("-w, --website <url>", "Website (full URL)").option("--street <street>", "Business street address").option("--city <city>", "City").option("--state <state>", "State, 2-letter code (carriers quote by state)").option("--zip <zip>", "ZIP code").option("-d, --description <text>", "A sentence on what the business does (helps carriers classify it)").option("--timeout <minutes>", "How long to wait for the email click", "15").option("--no-wait", 'Send the email and exit; collect later with "delegance verify"').action(async (options) => {
|
|
670
|
+
const existing = loadPendingSignup();
|
|
671
|
+
if (existing) {
|
|
672
|
+
warn(`A signup for ${existing.email} is already waiting for its email confirmation.`);
|
|
673
|
+
info('Run "delegance verify" to finish it, or "delegance verify --cancel" to start over.');
|
|
674
|
+
process.exit(1);
|
|
675
|
+
}
|
|
676
|
+
if (!(process.stdin.isTTY && process.stdout.isTTY)) {
|
|
677
|
+
const missing = missingSignupFields(options);
|
|
678
|
+
if (missing.length) {
|
|
679
|
+
error(`No terminal to ask in. Missing: ${missing.join(" ")}`);
|
|
680
|
+
process.exit(1);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
const answers = await import_inquirer.default.prompt(signupPrompts(options, Boolean(process.stdin.isTTY && process.stdout.isTTY)));
|
|
684
|
+
const address = {
|
|
685
|
+
street: String(options.street ?? answers.street).trim(),
|
|
686
|
+
city: String(options.city ?? answers.city).trim(),
|
|
687
|
+
state: String(options.state ?? answers.state).trim().toUpperCase(),
|
|
688
|
+
zip: String(options.zip ?? answers.zip).trim()
|
|
689
|
+
};
|
|
690
|
+
const badAddress = addressProblem(address);
|
|
691
|
+
if (badAddress) {
|
|
692
|
+
error(badAddress);
|
|
693
|
+
process.exit(1);
|
|
694
|
+
}
|
|
695
|
+
const description = String(options.description ?? answers.description ?? "").trim();
|
|
696
|
+
const email = String(options.email ?? answers.email).trim().toLowerCase();
|
|
697
|
+
if (!EMAIL_RE.test(email)) {
|
|
698
|
+
error("Enter a valid email with --email");
|
|
699
|
+
process.exit(1);
|
|
700
|
+
}
|
|
701
|
+
const size = String(options.size ?? answers.size);
|
|
702
|
+
if (!COMPANY_SIZES.includes(size)) {
|
|
703
|
+
error(`--size must be one of: ${COMPANY_SIZES.join(", ")}`);
|
|
704
|
+
process.exit(1);
|
|
705
|
+
}
|
|
706
|
+
const baseUrl = getBaseUrl();
|
|
707
|
+
const signup = (0, import_sdk2.createSignup)({ baseUrl });
|
|
708
|
+
const spinner = (0, import_ora.default)("Creating your account...").start();
|
|
709
|
+
try {
|
|
710
|
+
const account = await signup.createAccount({
|
|
711
|
+
name: String(options.business ?? answers.business).trim(),
|
|
712
|
+
contactEmail: email,
|
|
713
|
+
industry: String(options.industry ?? answers.industry).trim(),
|
|
714
|
+
companySize: size,
|
|
715
|
+
address,
|
|
716
|
+
...description ? { description } : {},
|
|
717
|
+
...options.website ? { website: options.website } : {}
|
|
718
|
+
});
|
|
719
|
+
const device = (0, import_sdk2.newDeviceSecret)();
|
|
720
|
+
let pending = {
|
|
721
|
+
enrollmentId: "",
|
|
722
|
+
deviceSecret: device.secret,
|
|
723
|
+
bootstrapKey: account.bootstrapKey,
|
|
724
|
+
companyId: account.companyId,
|
|
725
|
+
email,
|
|
726
|
+
firstName: String(options.first ?? answers.first).trim(),
|
|
727
|
+
lastName: String(options.last ?? answers.last).trim(),
|
|
728
|
+
baseUrl,
|
|
729
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
730
|
+
};
|
|
731
|
+
savePendingSignup(pending);
|
|
732
|
+
pending = await ensureEnrolled(pending);
|
|
733
|
+
spinner.succeed(`Account created. We emailed a confirm link to ${email}.`);
|
|
734
|
+
if (account.warnings?.includes("address_not_saved")) {
|
|
735
|
+
warn("We could not save your business address. Add it in the portal before quoting, or carriers cannot price you.");
|
|
736
|
+
}
|
|
737
|
+
if (options.wait === false) {
|
|
738
|
+
info('After you click it, run "delegance verify" to collect your API key.');
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
await waitAndStore(pending, Number(options.timeout) * 6e4 || 15 * 6e4);
|
|
742
|
+
} catch (err) {
|
|
743
|
+
spinner.stop();
|
|
744
|
+
failWith("Signup failed", err);
|
|
745
|
+
}
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
function resendFailureMessage(err) {
|
|
749
|
+
if (err instanceof import_sdk2.DeleganceError && (err.statusCode === 401 || err.statusCode === 403)) {
|
|
750
|
+
return "Your email is already confirmed, so there is nothing to re-send. Collecting your key now.";
|
|
751
|
+
}
|
|
752
|
+
const why = err instanceof Error ? err.message : String(err);
|
|
753
|
+
return `Could not re-send the email (${why}). Still waiting for the link you already have.`;
|
|
754
|
+
}
|
|
755
|
+
function createVerifyCommand() {
|
|
756
|
+
return new import_commander.Command("verify").description("Finish a signup: wait for the email click and store your API key").option("--resend", "Send the confirm email again").option("--cancel", "Forget the in-progress signup on this machine").option("--timeout <minutes>", "How long to wait", "15").action(async (options) => {
|
|
757
|
+
const pending = loadPendingSignup();
|
|
758
|
+
if (!pending) {
|
|
759
|
+
info('No signup is waiting on this machine. Start one with "delegance signup".');
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
if (options.cancel) {
|
|
763
|
+
clearPendingSignup();
|
|
764
|
+
success(`Forgot the signup for ${pending.email}.`);
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
let resumed = pending;
|
|
768
|
+
if (pending.enrollmentId && pending.verificationSent === false) {
|
|
769
|
+
try {
|
|
770
|
+
resumed = await sendVerification(pending);
|
|
771
|
+
success(`Sent the confirm link to ${pending.email}.`);
|
|
772
|
+
} catch (err) {
|
|
773
|
+
failWith("Could not send the confirm email", err);
|
|
774
|
+
}
|
|
775
|
+
} else if (!pending.enrollmentId) {
|
|
776
|
+
try {
|
|
777
|
+
resumed = await ensureEnrolled(pending);
|
|
778
|
+
success(`Finished setting up the signup. We emailed a confirm link to ${pending.email}.`);
|
|
779
|
+
} catch (err) {
|
|
780
|
+
failWith("Could not finish the signup", err);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
if (options.resend && resumed === pending) {
|
|
784
|
+
try {
|
|
785
|
+
await (0, import_sdk2.createSignup)({ baseUrl: pending.baseUrl }).requestVerification(pending.bootstrapKey);
|
|
786
|
+
success(`Sent the confirm link to ${pending.email} again.`);
|
|
787
|
+
} catch (err) {
|
|
788
|
+
info(resendFailureMessage(err));
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
await waitAndStore(resumed, Number(options.timeout) * 6e4 || 15 * 6e4);
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// src/commands/auth.ts
|
|
796
|
+
var KEY_FORMAT_HINT = "Expected format: dlg_xxxxxxxx_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
|
|
797
|
+
function defaultDeviceName() {
|
|
798
|
+
let user = "";
|
|
799
|
+
try {
|
|
800
|
+
user = os4.userInfo().username;
|
|
801
|
+
} catch {
|
|
802
|
+
}
|
|
803
|
+
const host = os4.hostname() || "terminal";
|
|
804
|
+
return (user ? `${host} (${user})` : host).slice(0, 64);
|
|
805
|
+
}
|
|
806
|
+
function describeExpiry(expiresAt, now = /* @__PURE__ */ new Date()) {
|
|
807
|
+
if (!expiresAt) return "Never";
|
|
808
|
+
const at = Date.parse(expiresAt);
|
|
809
|
+
if (!Number.isFinite(at)) return "Unknown";
|
|
810
|
+
const date = new Date(at).toLocaleDateString("en-US", { year: "numeric", month: "short", day: "numeric" });
|
|
811
|
+
const days = Math.ceil((at - now.getTime()) / (24 * 60 * 60 * 1e3));
|
|
812
|
+
if (days <= 0) return `${date} (expired)`;
|
|
813
|
+
return `${date} (in ${days} day${days === 1 ? "" : "s"})`;
|
|
814
|
+
}
|
|
815
|
+
function deviceLoginRefusal(err) {
|
|
816
|
+
if (!(err instanceof import_sdk3.DeleganceError)) return null;
|
|
817
|
+
switch (err.reason) {
|
|
818
|
+
case "access_denied":
|
|
819
|
+
return "The request was denied in the portal. Nothing was stored.";
|
|
820
|
+
case "expired":
|
|
821
|
+
return 'The code expired before it was approved. Run "delegance auth login" again.';
|
|
822
|
+
case "already_collected":
|
|
823
|
+
return 'This login was already used. Run "delegance auth login" again.';
|
|
824
|
+
default:
|
|
825
|
+
return null;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
function notAvailable(err) {
|
|
829
|
+
return err instanceof import_sdk3.DeleganceError && (err.statusCode === 404 || err.reason === "not_available");
|
|
830
|
+
}
|
|
831
|
+
function pasteFallbackHint() {
|
|
832
|
+
info(`Create a key at ${PORTAL_URL}/portal/settings/api-keys, then run "delegance auth login --key <key>".`);
|
|
833
|
+
}
|
|
834
|
+
async function readKeyInfo(apiKey, timeoutMs = 5e3) {
|
|
835
|
+
try {
|
|
836
|
+
const client = new import_sdk3.DeleganceClient({ apiKey, baseUrl: getBaseUrl(), timeout: timeoutMs, retries: 0 });
|
|
837
|
+
return await client.auth.keyInfo();
|
|
838
|
+
} catch {
|
|
839
|
+
return null;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
async function loginWithKey(apiKey, skipVerify) {
|
|
843
|
+
if (!API_KEY_REGEX.test(apiKey)) {
|
|
844
|
+
error("Invalid API key format");
|
|
845
|
+
info(KEY_FORMAT_HINT);
|
|
846
|
+
process.exit(1);
|
|
847
|
+
}
|
|
848
|
+
if (skipVerify) {
|
|
849
|
+
storeApiKey(apiKey);
|
|
850
|
+
success("API key stored (verification skipped)");
|
|
851
|
+
info(`API key stored in ${getCredentialsPath()}`);
|
|
852
|
+
warn("Key will be verified on first API call");
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
const spinner = (0, import_ora2.default)("Verifying API key...").start();
|
|
856
|
+
try {
|
|
857
|
+
const client = new import_sdk3.DeleganceClient({ apiKey, baseUrl: getBaseUrl() });
|
|
858
|
+
const isValid = await client.verifyApiKey();
|
|
859
|
+
if (!isValid) {
|
|
860
|
+
spinner.fail("API key verification failed");
|
|
861
|
+
error("The API key is invalid or expired");
|
|
862
|
+
info("Use --skip-verify to store anyway");
|
|
863
|
+
process.exit(1);
|
|
864
|
+
}
|
|
865
|
+
const keyInfo = await readKeyInfo(apiKey);
|
|
866
|
+
storeApiKey(apiKey, { expiresAt: keyInfo?.expiresAt ?? null });
|
|
867
|
+
spinner.succeed("API key verified");
|
|
868
|
+
success("Successfully authenticated!");
|
|
869
|
+
info(`API key stored in ${getCredentialsPath()}`);
|
|
870
|
+
} catch (err) {
|
|
871
|
+
spinner.fail("Failed to verify API key");
|
|
872
|
+
error("Could not connect to Delegance API", err.message);
|
|
873
|
+
info("Use --skip-verify to store anyway, or check DELEGANCE_BASE_URL");
|
|
874
|
+
process.exit(1);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
async function loginWithBrowser(openBrowser) {
|
|
878
|
+
const login = (0, import_sdk3.createDeviceLogin)({ baseUrl: getBaseUrl() });
|
|
879
|
+
const device = (0, import_sdk3.newDeviceSecret)();
|
|
880
|
+
let start;
|
|
881
|
+
try {
|
|
882
|
+
start = await login.start({ deviceSecretHash: device.hash, deviceName: defaultDeviceName() });
|
|
883
|
+
} catch (err) {
|
|
884
|
+
if (notAvailable(err)) {
|
|
885
|
+
error("Browser login is not available on this server yet.");
|
|
886
|
+
pasteFallbackHint();
|
|
887
|
+
process.exit(1);
|
|
888
|
+
}
|
|
889
|
+
failWith("Could not start the login", err);
|
|
890
|
+
}
|
|
891
|
+
console.log();
|
|
892
|
+
info("Approve this terminal in the Delegance portal:");
|
|
893
|
+
console.log(` ${start.verificationUriComplete}`);
|
|
894
|
+
console.log();
|
|
895
|
+
info(`Check that the page shows this code: ${start.userCode}`);
|
|
896
|
+
info("If the portal asks you to sign in first, open the link again after you sign in.");
|
|
897
|
+
if (openBrowser && process.stdout.isTTY) openInBrowser(start.verificationUriComplete);
|
|
898
|
+
console.log();
|
|
899
|
+
const spinner = (0, import_ora2.default)("Waiting for approval in the portal (Ctrl-C to cancel)...").start();
|
|
900
|
+
try {
|
|
901
|
+
const expiresIn = Date.parse(start.expiresAt) - Date.now();
|
|
902
|
+
const result = await login.waitForKey(start.loginId, device.secret, {
|
|
903
|
+
// The code waits 10 minutes; an approval then waits 5 more for this poll.
|
|
904
|
+
timeoutMs: Math.max(6e4, (Number.isFinite(expiresIn) ? expiresIn : 6e5) + 6e4),
|
|
905
|
+
intervalMs: Math.max(1, start.interval || 5) * 1e3
|
|
906
|
+
});
|
|
907
|
+
if (result.status !== "ready") {
|
|
908
|
+
spinner.fail("The code expired before it was approved.");
|
|
909
|
+
info('Run "delegance auth login" again.');
|
|
910
|
+
process.exit(1);
|
|
911
|
+
}
|
|
912
|
+
spinner.stop();
|
|
913
|
+
const stored = storeOrPrintKey(result.apiKey, (k) => storeApiKey(k, { expiresAt: result.expiresAt }));
|
|
914
|
+
setConfig("defaultCompanyId", result.companyId);
|
|
915
|
+
if (stored) success(`Signed in. API key ${maskApiKey(result.apiKey)} saved to ${getCredentialsPath()}`);
|
|
916
|
+
keyValue({
|
|
917
|
+
Company: result.companyId,
|
|
918
|
+
Access: result.scopes.join(", ") || "read",
|
|
919
|
+
Expires: describeExpiry(result.expiresAt)
|
|
920
|
+
});
|
|
921
|
+
info("The key stays valid while you use it; 90 days without use and it expires.");
|
|
922
|
+
} catch (err) {
|
|
923
|
+
spinner.stop();
|
|
924
|
+
const why = deviceLoginRefusal(err);
|
|
925
|
+
if (why) {
|
|
926
|
+
error(why);
|
|
927
|
+
process.exit(1);
|
|
928
|
+
}
|
|
929
|
+
failWith("Login failed", err);
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
function createAuthCommand() {
|
|
933
|
+
const auth = new import_commander2.Command("auth").description("Manage authentication");
|
|
934
|
+
auth.command("login").description("Sign in: approve this terminal in the portal, or store an API key").option("-k, --key <apiKey>", "Store this API key instead of signing in with the browser").option("--paste", "Prompt for an API key instead of signing in with the browser").option("--no-browser", "Print the approval link without opening a browser").option("--skip-verify", "With --key or --paste: store without verification (useful when API is not reachable)").action(async (options) => {
|
|
935
|
+
try {
|
|
936
|
+
if (options.key) {
|
|
937
|
+
await loginWithKey(String(options.key).trim(), Boolean(options.skipVerify));
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
if (options.paste) {
|
|
941
|
+
const answers = await import_inquirer2.default.prompt([
|
|
942
|
+
{
|
|
943
|
+
type: "password",
|
|
944
|
+
name: "apiKey",
|
|
945
|
+
message: "Enter your API key:",
|
|
946
|
+
mask: "*",
|
|
947
|
+
validate: (input) => {
|
|
948
|
+
if (!input) return "API key is required";
|
|
949
|
+
if (!API_KEY_REGEX.test(input)) return `Invalid API key format. ${KEY_FORMAT_HINT}`;
|
|
950
|
+
return true;
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
]);
|
|
954
|
+
await loginWithKey(answers.apiKey, Boolean(options.skipVerify));
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
await loginWithBrowser(options.browser !== false);
|
|
958
|
+
} catch (err) {
|
|
959
|
+
failWith("Authentication failed", err);
|
|
960
|
+
}
|
|
961
|
+
});
|
|
962
|
+
auth.command("status").description("Show the stored key, where it lives, and when it expires").action(async () => {
|
|
963
|
+
header("Authentication Status");
|
|
964
|
+
if (!isAuthenticated()) {
|
|
965
|
+
warn("Not authenticated");
|
|
966
|
+
info('Run "delegance auth login" to sign in, or "delegance signup" to create an account');
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
const apiKey = getApiKey() || "";
|
|
970
|
+
const fromEnv = Boolean(process.env.DELEGANCE_API_KEY);
|
|
971
|
+
const cached = fromEnv ? void 0 : getConfigValue("apiKeyExpiresAt");
|
|
972
|
+
let expiryLine = cached ? `${describeExpiry(cached)} (last known)` : "Unknown (could not reach the server)";
|
|
973
|
+
let origin = null;
|
|
974
|
+
let refused = null;
|
|
975
|
+
try {
|
|
976
|
+
const client = new import_sdk3.DeleganceClient({ apiKey, baseUrl: getBaseUrl(), timeout: 5e3, retries: 0 });
|
|
977
|
+
const keyInfo = await client.auth.keyInfo();
|
|
978
|
+
origin = keyInfo.origin;
|
|
979
|
+
expiryLine = describeExpiry(keyInfo.expiresAt);
|
|
980
|
+
if (!fromEnv) setStoredKeyExpiry(keyInfo.expiresAt);
|
|
981
|
+
} catch (err) {
|
|
982
|
+
if (err instanceof import_sdk3.DeleganceError && err.statusCode === 401) {
|
|
983
|
+
refused = err.reason === "key_expired" ? "Your API key expired" : err.reason === "key_membership_revoked" ? "Your API key was revoked: its user is no longer an active member of the company" : "Your API key was rejected by the server";
|
|
984
|
+
expiryLine = err.reason === "key_expired" ? "Expired" : "Not valid";
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
if (refused) {
|
|
988
|
+
warn(refused);
|
|
989
|
+
info('Run "delegance auth login" to sign in again.');
|
|
990
|
+
} else {
|
|
991
|
+
success("Authenticated");
|
|
992
|
+
}
|
|
993
|
+
console.log();
|
|
994
|
+
keyValue({
|
|
995
|
+
"API Key": maskApiKey(apiKey),
|
|
996
|
+
"Source": fromEnv ? "DELEGANCE_API_KEY" : getCredentialsPath(),
|
|
997
|
+
...origin ? { "Created by": origin === "cli_login" ? "browser login" : origin === "cli_signup" ? "CLI signup" : origin } : {},
|
|
998
|
+
"Expires": expiryLine,
|
|
999
|
+
"Base URL": getBaseUrl(),
|
|
1000
|
+
"Config File": getConfigPath()
|
|
1001
|
+
});
|
|
1002
|
+
});
|
|
1003
|
+
auth.command("refresh").description("Extend a CLI key to 90 days from now").action(async () => {
|
|
1004
|
+
const apiKey = getApiKey();
|
|
1005
|
+
if (!apiKey) {
|
|
1006
|
+
error("Not authenticated");
|
|
1007
|
+
info('Run "delegance auth login" to sign in');
|
|
1008
|
+
process.exit(1);
|
|
1009
|
+
}
|
|
1010
|
+
const spinner = (0, import_ora2.default)("Refreshing your key...").start();
|
|
1011
|
+
try {
|
|
1012
|
+
const client = new import_sdk3.DeleganceClient({ apiKey, baseUrl: getBaseUrl() });
|
|
1013
|
+
const { expiresAt } = await client.auth.refresh();
|
|
1014
|
+
if (!process.env.DELEGANCE_API_KEY) setStoredKeyExpiry(expiresAt);
|
|
1015
|
+
spinner.succeed(`Key refreshed. Expires ${describeExpiry(expiresAt)}.`);
|
|
1016
|
+
} catch (err) {
|
|
1017
|
+
spinner.stop();
|
|
1018
|
+
if (err instanceof import_sdk3.DeleganceError && err.reason === "not_refreshable") {
|
|
1019
|
+
info("This key came from the portal and keeps the expiry it was created with. Nothing changed.");
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
if (notAvailable(err)) {
|
|
1023
|
+
error("Key refresh is not available on this server yet.");
|
|
1024
|
+
process.exit(1);
|
|
1025
|
+
}
|
|
1026
|
+
failWith("Could not refresh the key", err);
|
|
1027
|
+
}
|
|
1028
|
+
});
|
|
1029
|
+
auth.command("logout").description("Remove stored credentials").option("-f, --force", "Skip confirmation").action(async (options) => {
|
|
1030
|
+
if (!isAuthenticated()) {
|
|
1031
|
+
info("Not currently authenticated");
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
if (!options.force) {
|
|
1035
|
+
const { confirm } = await import_inquirer2.default.prompt([
|
|
1036
|
+
{
|
|
1037
|
+
type: "confirm",
|
|
1038
|
+
name: "confirm",
|
|
1039
|
+
message: "Are you sure you want to remove your credentials?",
|
|
1040
|
+
default: false
|
|
1041
|
+
}
|
|
1042
|
+
]);
|
|
1043
|
+
if (!confirm) {
|
|
1044
|
+
info("Logout cancelled");
|
|
1045
|
+
return;
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
clearCredentials();
|
|
1049
|
+
success("Successfully logged out");
|
|
1050
|
+
info("API key removed from local storage");
|
|
1051
|
+
});
|
|
1052
|
+
auth.command("whoami").description("Show current authenticated identity").action(async () => {
|
|
1053
|
+
if (!isAuthenticated()) {
|
|
1054
|
+
error("Not authenticated");
|
|
1055
|
+
info('Run "delegance auth login" to authenticate');
|
|
1056
|
+
process.exit(1);
|
|
1057
|
+
}
|
|
1058
|
+
const apiKey = getApiKey();
|
|
1059
|
+
if (!apiKey) {
|
|
1060
|
+
error("No API key found");
|
|
1061
|
+
process.exit(1);
|
|
1062
|
+
}
|
|
1063
|
+
const spinner = (0, import_ora2.default)("Fetching account info...").start();
|
|
1064
|
+
try {
|
|
1065
|
+
const client = new import_sdk3.DeleganceClient({
|
|
1066
|
+
apiKey,
|
|
1067
|
+
baseUrl: getBaseUrl()
|
|
1068
|
+
});
|
|
1069
|
+
const companies = await client.companies.list();
|
|
1070
|
+
spinner.succeed("Account info retrieved");
|
|
1071
|
+
console.log();
|
|
1072
|
+
if (companies.length > 0) {
|
|
1073
|
+
const company = companies[0];
|
|
1074
|
+
keyValue({
|
|
1075
|
+
"Company": company.name,
|
|
1076
|
+
"Company ID": company.id,
|
|
1077
|
+
"Industry": company.industry,
|
|
1078
|
+
"Contact": company.contactEmail,
|
|
1079
|
+
"API Key": maskApiKey(apiKey)
|
|
1080
|
+
});
|
|
1081
|
+
} else {
|
|
1082
|
+
warn("No company found for this API key");
|
|
1083
|
+
}
|
|
1084
|
+
} catch (err) {
|
|
1085
|
+
spinner.fail("Failed to fetch account info");
|
|
1086
|
+
failWith("API request failed", err);
|
|
1087
|
+
}
|
|
1088
|
+
});
|
|
1089
|
+
return auth;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
// src/commands/company.ts
|
|
1093
|
+
var import_commander3 = require("commander");
|
|
1094
|
+
var import_inquirer3 = __toESM(require("inquirer"));
|
|
1095
|
+
var import_ora3 = __toESM(require("ora"));
|
|
1096
|
+
function createCompanyCommand() {
|
|
1097
|
+
const company = new import_commander3.Command("company").alias("companies").description("Manage companies");
|
|
1098
|
+
company.command("list").description("List companies").option("--include-created", "Include companies created via this API key").option("--json", "Output as JSON").action(async (options) => {
|
|
1099
|
+
const client = getClient();
|
|
1100
|
+
const spinner = (0, import_ora3.default)("Fetching companies...").start();
|
|
1101
|
+
try {
|
|
1102
|
+
const companies = await client.companies.list({
|
|
1103
|
+
includeCreated: options.includeCreated
|
|
1104
|
+
});
|
|
1105
|
+
spinner.succeed(`Found ${companies.length} companies`);
|
|
1106
|
+
if (companies.length === 0) {
|
|
1107
|
+
info("No companies found");
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1110
|
+
if (options.json) {
|
|
1111
|
+
json(companies);
|
|
1112
|
+
} else {
|
|
1113
|
+
console.log();
|
|
1114
|
+
table(
|
|
1115
|
+
["ID", "Name", "Industry", "Size", "Created"],
|
|
1116
|
+
companies.map((c) => [
|
|
1117
|
+
`${c.id.substring(0, 8)}...`,
|
|
1118
|
+
c.name,
|
|
1119
|
+
c.industry,
|
|
1120
|
+
c.companySize,
|
|
1121
|
+
formatDate(c.createdAt)
|
|
1122
|
+
])
|
|
1123
|
+
);
|
|
1124
|
+
}
|
|
1125
|
+
} catch (err) {
|
|
1126
|
+
spinner.fail("Failed to list companies");
|
|
1127
|
+
failWith("API error", err);
|
|
1128
|
+
}
|
|
1129
|
+
});
|
|
1130
|
+
company.command("create").description("Create a new company").option("-n, --name <name>", "Company name").option("-i, --industry <industry>", "Industry").option("-s, --size <size>", "Company size (1, 2-10, 11-25, 26-50, 51-100, 100-499, 500+)").option("-e, --email <email>", "Contact email").option("--fein <fein>", "FEIN (XX-XXXXXXX)").option("--website <url>", "Website URL").option("--external-id <id>", "External ID for deduplication").option("--interactive", "Interactive mode with prompts").option("--json", "Output as JSON").action(async (options) => {
|
|
1131
|
+
let companyData;
|
|
1132
|
+
if (options.interactive || !options.name && !options.industry) {
|
|
1133
|
+
const answers = await import_inquirer3.default.prompt([
|
|
1134
|
+
{
|
|
1135
|
+
type: "input",
|
|
1136
|
+
name: "name",
|
|
1137
|
+
message: "Company name:",
|
|
1138
|
+
default: options.name,
|
|
1139
|
+
validate: (input) => input.length > 0 || "Name is required"
|
|
1140
|
+
},
|
|
1141
|
+
{
|
|
1142
|
+
type: "input",
|
|
1143
|
+
name: "industry",
|
|
1144
|
+
message: "Industry:",
|
|
1145
|
+
default: options.industry,
|
|
1146
|
+
validate: (input) => input.length > 0 || "Industry is required"
|
|
1147
|
+
},
|
|
1148
|
+
{
|
|
1149
|
+
type: "list",
|
|
1150
|
+
name: "companySize",
|
|
1151
|
+
message: "Company size:",
|
|
1152
|
+
choices: ["1", "2-10", "11-25", "26-50", "51-100", "100-499", "500+"],
|
|
1153
|
+
default: options.size
|
|
1154
|
+
},
|
|
1155
|
+
{
|
|
1156
|
+
type: "input",
|
|
1157
|
+
name: "contactEmail",
|
|
1158
|
+
message: "Contact email:",
|
|
1159
|
+
default: options.email,
|
|
1160
|
+
validate: (input) => {
|
|
1161
|
+
if (!input) return "Email is required";
|
|
1162
|
+
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(input)) return "Invalid email format";
|
|
1163
|
+
return true;
|
|
1164
|
+
}
|
|
1165
|
+
},
|
|
1166
|
+
{
|
|
1167
|
+
type: "input",
|
|
1168
|
+
name: "fein",
|
|
1169
|
+
message: "FEIN (XX-XXXXXXX, optional):",
|
|
1170
|
+
default: options.fein
|
|
1171
|
+
},
|
|
1172
|
+
{
|
|
1173
|
+
type: "input",
|
|
1174
|
+
name: "website",
|
|
1175
|
+
message: "Website (optional):",
|
|
1176
|
+
default: options.website
|
|
1177
|
+
}
|
|
1178
|
+
]);
|
|
1179
|
+
companyData = {
|
|
1180
|
+
name: answers.name,
|
|
1181
|
+
industry: answers.industry,
|
|
1182
|
+
companySize: answers.companySize,
|
|
1183
|
+
contactEmail: answers.contactEmail,
|
|
1184
|
+
fein: answers.fein || void 0,
|
|
1185
|
+
website: answers.website || void 0,
|
|
1186
|
+
externalId: options.externalId
|
|
1187
|
+
};
|
|
1188
|
+
} else {
|
|
1189
|
+
if (!options.name || !options.industry || !options.size || !options.email) {
|
|
1190
|
+
error("Missing required options");
|
|
1191
|
+
info("Required: --name, --industry, --size, --email");
|
|
1192
|
+
info("Or use --interactive for guided input");
|
|
1193
|
+
process.exit(1);
|
|
1194
|
+
}
|
|
1195
|
+
companyData = {
|
|
1196
|
+
name: options.name,
|
|
1197
|
+
industry: options.industry,
|
|
1198
|
+
companySize: options.size,
|
|
1199
|
+
contactEmail: options.email,
|
|
1200
|
+
fein: options.fein,
|
|
1201
|
+
website: options.website,
|
|
1202
|
+
externalId: options.externalId
|
|
1203
|
+
};
|
|
1204
|
+
}
|
|
1205
|
+
const client = getClient();
|
|
1206
|
+
const spinner = (0, import_ora3.default)("Creating company...").start();
|
|
1207
|
+
try {
|
|
1208
|
+
const company2 = await client.companies.create(companyData);
|
|
1209
|
+
spinner.succeed("Company created");
|
|
1210
|
+
if (options.json) {
|
|
1211
|
+
json(company2);
|
|
1212
|
+
} else {
|
|
1213
|
+
console.log();
|
|
1214
|
+
success(`Created company: ${company2.name}`);
|
|
1215
|
+
keyValue({
|
|
1216
|
+
"ID": company2.id,
|
|
1217
|
+
"Name": company2.name,
|
|
1218
|
+
"Industry": company2.industry,
|
|
1219
|
+
"Size": company2.companySize,
|
|
1220
|
+
"Contact": company2.contactEmail,
|
|
1221
|
+
"Created": formatDate(company2.createdAt)
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1224
|
+
} catch (err) {
|
|
1225
|
+
spinner.fail("Failed to create company");
|
|
1226
|
+
failWith("API error", err);
|
|
1227
|
+
}
|
|
1228
|
+
});
|
|
1229
|
+
company.command("use <companyId>").description("Set default company for subsequent commands").action(async (companyId) => {
|
|
1230
|
+
const client = getClient();
|
|
1231
|
+
const spinner = (0, import_ora3.default)("Verifying company...").start();
|
|
1232
|
+
try {
|
|
1233
|
+
const companies = await client.companies.list({ includeCreated: true });
|
|
1234
|
+
const found = companies.find((c) => c.id === companyId || c.id.startsWith(companyId));
|
|
1235
|
+
if (!found) {
|
|
1236
|
+
spinner.fail("Company not found");
|
|
1237
|
+
error(`No company found with ID starting with: ${companyId}`);
|
|
1238
|
+
process.exit(1);
|
|
1239
|
+
}
|
|
1240
|
+
setConfig("defaultCompanyId", found.id);
|
|
1241
|
+
spinner.succeed(`Default company set to: ${found.name}`);
|
|
1242
|
+
} catch (err) {
|
|
1243
|
+
spinner.fail("Failed to verify company");
|
|
1244
|
+
failWith("API error", err);
|
|
1245
|
+
}
|
|
1246
|
+
});
|
|
1247
|
+
company.command("current").description("Show current default company").action(async () => {
|
|
1248
|
+
const companyId = getConfigValue("defaultCompanyId");
|
|
1249
|
+
if (!companyId) {
|
|
1250
|
+
info("No default company set");
|
|
1251
|
+
info('Use "delegance company use <id>" to set one');
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1254
|
+
const client = getClient();
|
|
1255
|
+
const spinner = (0, import_ora3.default)("Fetching company details...").start();
|
|
1256
|
+
try {
|
|
1257
|
+
const companies = await client.companies.list({ includeCreated: true });
|
|
1258
|
+
const company2 = companies.find((c) => c.id === companyId);
|
|
1259
|
+
if (!company2) {
|
|
1260
|
+
spinner.warn("Default company no longer accessible");
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
1263
|
+
spinner.succeed("Current default company");
|
|
1264
|
+
console.log();
|
|
1265
|
+
keyValue({
|
|
1266
|
+
"ID": company2.id,
|
|
1267
|
+
"Name": company2.name,
|
|
1268
|
+
"Industry": company2.industry
|
|
1269
|
+
});
|
|
1270
|
+
} catch (err) {
|
|
1271
|
+
spinner.fail("Failed to fetch company");
|
|
1272
|
+
failWith("API error", err);
|
|
1273
|
+
}
|
|
1274
|
+
});
|
|
1275
|
+
return company;
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
// src/commands/document.ts
|
|
1279
|
+
var import_commander4 = require("commander");
|
|
1280
|
+
var import_ora4 = __toESM(require("ora"));
|
|
1281
|
+
var fs4 = __toESM(require("fs"));
|
|
1282
|
+
var path4 = __toESM(require("path"));
|
|
1283
|
+
var import_glob = require("glob");
|
|
1284
|
+
function extractionOutcome(status) {
|
|
1285
|
+
if (status === "completed" || status === "processed") return "done";
|
|
1286
|
+
if (status === "error" || status === "failed") return "failed";
|
|
1287
|
+
return "pending";
|
|
1288
|
+
}
|
|
1289
|
+
function createDocumentCommand() {
|
|
1290
|
+
const doc = new import_commander4.Command("doc").alias("docs").alias("document").alias("documents").description("Manage documents");
|
|
1291
|
+
doc.command("upload <files...>").description("Upload one or more documents").option("-c, --company <id>", "Company ID (or use default)").option("-t, --type <type>", "Document type (policy, loss_run, financial_statement, sov, application, certificate, endorsement, bor, other)").option("--external-id <id>", "External ID for reference").option("-w, --wait", "Wait for extraction to complete").option("--timeout <ms>", "Timeout for waiting (default: 120000)", "120000").option("--json", "Output as JSON").action(async (filePatterns, options) => {
|
|
1292
|
+
const companyId = getCompanyId(options);
|
|
1293
|
+
const client = getClient();
|
|
1294
|
+
let files = [];
|
|
1295
|
+
for (const pattern of filePatterns) {
|
|
1296
|
+
if (pattern.includes("*")) {
|
|
1297
|
+
const matches = await (0, import_glob.glob)(pattern);
|
|
1298
|
+
files.push(...matches);
|
|
1299
|
+
} else {
|
|
1300
|
+
files.push(pattern);
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
files = files.filter((f) => {
|
|
1304
|
+
if (!fs4.existsSync(f)) {
|
|
1305
|
+
warn(`File not found: ${f}`);
|
|
1306
|
+
return false;
|
|
1307
|
+
}
|
|
1308
|
+
if (fs4.statSync(f).isDirectory()) {
|
|
1309
|
+
warn(`Skipping directory: ${f}`);
|
|
1310
|
+
return false;
|
|
1311
|
+
}
|
|
1312
|
+
return true;
|
|
1313
|
+
});
|
|
1314
|
+
if (files.length === 0) {
|
|
1315
|
+
error("No valid files to upload");
|
|
1316
|
+
process.exit(1);
|
|
1317
|
+
}
|
|
1318
|
+
info(`Uploading ${files.length} file(s) to company ${companyId.substring(0, 8)}...`);
|
|
1319
|
+
console.log();
|
|
1320
|
+
const results = [];
|
|
1321
|
+
for (const filePath of files) {
|
|
1322
|
+
const fileName = path4.basename(filePath);
|
|
1323
|
+
const spinner = (0, import_ora4.default)(`Uploading ${fileName}...`).start();
|
|
1324
|
+
try {
|
|
1325
|
+
const fileBuffer = fs4.readFileSync(filePath);
|
|
1326
|
+
const document = await client.documents.upload({
|
|
1327
|
+
companyId,
|
|
1328
|
+
file: fileBuffer,
|
|
1329
|
+
fileName,
|
|
1330
|
+
documentType: options.type,
|
|
1331
|
+
externalId: options.externalId
|
|
1332
|
+
});
|
|
1333
|
+
spinner.succeed(`Uploaded ${fileName} (${document.id.substring(0, 8)}...)`);
|
|
1334
|
+
results.push({ file: fileName, docId: document.id, status: "uploaded" });
|
|
1335
|
+
if (options.wait) {
|
|
1336
|
+
const waitSpinner = (0, import_ora4.default)(` Processing ${fileName}...`).start();
|
|
1337
|
+
try {
|
|
1338
|
+
const result = await client.documents.waitForExtraction(document.id, {
|
|
1339
|
+
timeoutMs: parseInt(options.timeout),
|
|
1340
|
+
onStatusChange: (status) => {
|
|
1341
|
+
waitSpinner.text = ` Processing ${fileName}: ${status}`;
|
|
1342
|
+
}
|
|
1343
|
+
});
|
|
1344
|
+
if (result.status === "completed" || result.status === "processed") {
|
|
1345
|
+
waitSpinner.succeed(` Processed ${fileName}: ${result.extraction?.policies.length || 0} policies found`);
|
|
1346
|
+
results[results.length - 1].status = "completed";
|
|
1347
|
+
} else {
|
|
1348
|
+
waitSpinner.warn(` Processing ${fileName}: ${result.status}`);
|
|
1349
|
+
results[results.length - 1].status = result.status;
|
|
1350
|
+
}
|
|
1351
|
+
} catch (waitErr) {
|
|
1352
|
+
waitSpinner.fail(` Processing failed: ${waitErr.message}`);
|
|
1353
|
+
results[results.length - 1].status = "error";
|
|
1354
|
+
results[results.length - 1].error = waitErr.message;
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
} catch (err) {
|
|
1358
|
+
spinner.fail(`Failed to upload ${fileName}`);
|
|
1359
|
+
results.push({ file: fileName, status: "error", error: err.message });
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
console.log();
|
|
1363
|
+
const successCount = results.filter((r) => r.status !== "error").length;
|
|
1364
|
+
const errorCount = results.filter((r) => r.status === "error").length;
|
|
1365
|
+
if (errorCount === 0) {
|
|
1366
|
+
success(`Successfully uploaded ${successCount} file(s)`);
|
|
1367
|
+
} else {
|
|
1368
|
+
warn(`Uploaded ${successCount} file(s), ${errorCount} failed`);
|
|
1369
|
+
}
|
|
1370
|
+
if (options.json) {
|
|
1371
|
+
json(results);
|
|
1372
|
+
}
|
|
1373
|
+
});
|
|
1374
|
+
doc.command("list").description("List documents").option("-c, --company <id>", "Company ID (or use default)").option("-t, --type <type>", "Filter by document type").option("-s, --status <status>", "Filter by status").option("-l, --limit <n>", "Maximum results (default: 50)", "50").option("--json", "Output as JSON").action(async (options) => {
|
|
1375
|
+
const companyId = getCompanyId(options);
|
|
1376
|
+
const client = getClient();
|
|
1377
|
+
const spinner = (0, import_ora4.default)("Fetching documents...").start();
|
|
1378
|
+
try {
|
|
1379
|
+
const { documents, pagination } = await client.documents.list({
|
|
1380
|
+
companyId,
|
|
1381
|
+
documentType: options.type,
|
|
1382
|
+
status: options.status,
|
|
1383
|
+
limit: parseInt(options.limit)
|
|
1384
|
+
});
|
|
1385
|
+
spinner.succeed(`Found ${pagination.total} documents`);
|
|
1386
|
+
if (documents.length === 0) {
|
|
1387
|
+
info("No documents found");
|
|
1388
|
+
return;
|
|
1389
|
+
}
|
|
1390
|
+
if (options.json) {
|
|
1391
|
+
json({ documents, pagination });
|
|
1392
|
+
} else {
|
|
1393
|
+
console.log();
|
|
1394
|
+
table(
|
|
1395
|
+
["ID", "File Name", "Type", "Status", "Size", "Created"],
|
|
1396
|
+
documents.map((d) => [
|
|
1397
|
+
`${d.id.substring(0, 8)}...`,
|
|
1398
|
+
d.fileName.length > 30 ? `${d.fileName.substring(0, 27)}...` : d.fileName,
|
|
1399
|
+
d.documentType || "-",
|
|
1400
|
+
formatStatus(d.status),
|
|
1401
|
+
formatFileSize(d.fileSize),
|
|
1402
|
+
formatDate(d.createdAt)
|
|
1403
|
+
])
|
|
1404
|
+
);
|
|
1405
|
+
if (pagination.hasMore) {
|
|
1406
|
+
console.log();
|
|
1407
|
+
info(`Showing ${documents.length} of ${pagination.total}. Use --limit to see more.`);
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
} catch (err) {
|
|
1411
|
+
spinner.fail("Failed to list documents");
|
|
1412
|
+
failWith("API error", err);
|
|
1413
|
+
}
|
|
1414
|
+
});
|
|
1415
|
+
doc.command("status <documentId>").alias("get").description("Get document details and extraction status").option("--json", "Output as JSON").action(async (documentId, options) => {
|
|
1416
|
+
const client = getClient();
|
|
1417
|
+
const spinner = (0, import_ora4.default)("Fetching document...").start();
|
|
1418
|
+
try {
|
|
1419
|
+
const document = await client.documents.get(documentId);
|
|
1420
|
+
spinner.succeed("Document retrieved");
|
|
1421
|
+
if (options.json) {
|
|
1422
|
+
json(document);
|
|
1423
|
+
} else {
|
|
1424
|
+
console.log();
|
|
1425
|
+
header("Document Details");
|
|
1426
|
+
keyValue({
|
|
1427
|
+
"ID": document.id,
|
|
1428
|
+
"File Name": document.fileName,
|
|
1429
|
+
"Type": document.documentType || "(not classified)",
|
|
1430
|
+
"Status": formatStatus(document.status),
|
|
1431
|
+
"Size": formatFileSize(document.fileSize),
|
|
1432
|
+
"Created": formatDate(document.createdAt),
|
|
1433
|
+
"Updated": formatDate(document.updatedAt)
|
|
1434
|
+
});
|
|
1435
|
+
if (document.extraction) {
|
|
1436
|
+
console.log();
|
|
1437
|
+
header("Extraction Results");
|
|
1438
|
+
keyValue({
|
|
1439
|
+
"Has Text": document.extraction.hasText,
|
|
1440
|
+
"Text Length": document.extraction.textLength,
|
|
1441
|
+
"Policies Found": document.extraction.policies.length
|
|
1442
|
+
});
|
|
1443
|
+
if (document.extraction.policies.length > 0) {
|
|
1444
|
+
console.log();
|
|
1445
|
+
info("Extracted Policies:");
|
|
1446
|
+
table(
|
|
1447
|
+
["ID", "Type", "Number", "Carrier", "Effective", "Expiration"],
|
|
1448
|
+
document.extraction.policies.map((p) => [
|
|
1449
|
+
`${p.id.substring(0, 8)}...`,
|
|
1450
|
+
p.policyType,
|
|
1451
|
+
p.policyNumber || "-",
|
|
1452
|
+
p.carrierName || "-",
|
|
1453
|
+
p.effectiveDate || "-",
|
|
1454
|
+
p.expirationDate || "-"
|
|
1455
|
+
])
|
|
1456
|
+
);
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
if (document.processing) {
|
|
1460
|
+
console.log();
|
|
1461
|
+
info(document.processing.message);
|
|
1462
|
+
}
|
|
1463
|
+
if (document.error) {
|
|
1464
|
+
console.log();
|
|
1465
|
+
error("Processing Error", document.error.message);
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
} catch (err) {
|
|
1469
|
+
spinner.fail("Failed to get document");
|
|
1470
|
+
failWith("API error", err);
|
|
1471
|
+
}
|
|
1472
|
+
});
|
|
1473
|
+
doc.command("wait <documentId>").description("Wait for document processing to complete").option("--timeout <ms>", "Timeout in milliseconds (default: 120000)", "120000").option("--json", "Output as JSON").action(async (documentId, options) => {
|
|
1474
|
+
const client = getClient();
|
|
1475
|
+
const spinner = (0, import_ora4.default)("Waiting for processing...").start();
|
|
1476
|
+
try {
|
|
1477
|
+
const result = await client.documents.waitForExtraction(documentId, {
|
|
1478
|
+
timeoutMs: parseInt(options.timeout),
|
|
1479
|
+
onStatusChange: (status) => {
|
|
1480
|
+
spinner.text = `Processing: ${status}`;
|
|
1481
|
+
}
|
|
1482
|
+
});
|
|
1483
|
+
const outcome = extractionOutcome(result.status);
|
|
1484
|
+
if (outcome === "done") {
|
|
1485
|
+
spinner.succeed("Processing complete");
|
|
1486
|
+
if (options.json) {
|
|
1487
|
+
json(result);
|
|
1488
|
+
} else {
|
|
1489
|
+
info(`Found ${result.extraction?.policies.length || 0} policies`);
|
|
1490
|
+
}
|
|
1491
|
+
} else if (outcome === "failed") {
|
|
1492
|
+
spinner.fail("Processing failed");
|
|
1493
|
+
error(result.error?.message || "Unknown error");
|
|
1494
|
+
process.exit(1);
|
|
1495
|
+
} else {
|
|
1496
|
+
spinner.warn(`Processing status: ${result.status}`);
|
|
1497
|
+
info("Document is still being processed. Try again later.");
|
|
1498
|
+
}
|
|
1499
|
+
} catch (err) {
|
|
1500
|
+
spinner.fail("Wait failed");
|
|
1501
|
+
failWith("Error", err);
|
|
1502
|
+
}
|
|
1503
|
+
});
|
|
1504
|
+
doc.command("reprocess <documentId>").description("Re-run extraction on a document (limited retries per day)").option("-w, --wait", "Wait for extraction to complete").option("--timeout <ms>", "Timeout for waiting (default: 120000)", "120000").option("--json", "Output as JSON").action(async (documentId, options) => {
|
|
1505
|
+
const client = getClient();
|
|
1506
|
+
const spinner = (0, import_ora4.default)("Requesting reprocess...").start();
|
|
1507
|
+
try {
|
|
1508
|
+
const result = await client.documents.reprocess(documentId);
|
|
1509
|
+
spinner.succeed(result.message);
|
|
1510
|
+
if (options.wait) {
|
|
1511
|
+
const waitSpinner = (0, import_ora4.default)("Waiting for extraction...").start();
|
|
1512
|
+
const finished = await client.documents.waitForExtraction(documentId, {
|
|
1513
|
+
timeoutMs: parseInt(options.timeout)
|
|
1514
|
+
});
|
|
1515
|
+
const outcome = extractionOutcome(finished.status);
|
|
1516
|
+
if (options.json) json({ ...result, document: finished });
|
|
1517
|
+
if (outcome === "done") {
|
|
1518
|
+
waitSpinner.succeed(`Extraction ${finished.status}`);
|
|
1519
|
+
} else if (outcome === "failed") {
|
|
1520
|
+
waitSpinner.fail(`Extraction ${finished.status}`);
|
|
1521
|
+
if (!options.json) error(finished.error?.message || "Unknown error");
|
|
1522
|
+
process.exit(1);
|
|
1523
|
+
} else {
|
|
1524
|
+
waitSpinner.warn(`Extraction still ${finished.status}`);
|
|
1525
|
+
if (!options.json) info(`Run "delegance doc wait ${documentId}" to keep following it`);
|
|
1526
|
+
}
|
|
1527
|
+
return;
|
|
1528
|
+
}
|
|
1529
|
+
if (options.json) {
|
|
1530
|
+
json(result);
|
|
1531
|
+
} else {
|
|
1532
|
+
info(`${result.reprocessesRemaining} reprocess attempts left today`);
|
|
1533
|
+
info(`Run "delegance doc wait ${documentId}" to follow it`);
|
|
1534
|
+
}
|
|
1535
|
+
} catch (err) {
|
|
1536
|
+
spinner.fail("Failed to reprocess document");
|
|
1537
|
+
failWith("API error", err);
|
|
1538
|
+
}
|
|
1539
|
+
});
|
|
1540
|
+
doc.command("archive <documentId>").description("Archive a document (hide it from lists and answers)").option("--json", "Output as JSON").action(async (documentId, options) => {
|
|
1541
|
+
const client = getClient();
|
|
1542
|
+
const spinner = (0, import_ora4.default)("Archiving document...").start();
|
|
1543
|
+
try {
|
|
1544
|
+
const result = await client.documents.archive(documentId);
|
|
1545
|
+
spinner.succeed(result.message || "Document archived");
|
|
1546
|
+
if (options.json) json(result);
|
|
1547
|
+
} catch (err) {
|
|
1548
|
+
spinner.fail("Failed to archive document");
|
|
1549
|
+
failWith("API error", err);
|
|
1550
|
+
}
|
|
1551
|
+
});
|
|
1552
|
+
doc.command("unarchive <documentId>").description("Restore an archived document").option("--json", "Output as JSON").action(async (documentId, options) => {
|
|
1553
|
+
const client = getClient();
|
|
1554
|
+
const spinner = (0, import_ora4.default)("Restoring document...").start();
|
|
1555
|
+
try {
|
|
1556
|
+
const result = await client.documents.unarchive(documentId);
|
|
1557
|
+
spinner.succeed(result.message || "Document restored");
|
|
1558
|
+
if (options.json) json(result);
|
|
1559
|
+
} catch (err) {
|
|
1560
|
+
spinner.fail("Failed to restore document");
|
|
1561
|
+
failWith("API error", err);
|
|
1562
|
+
}
|
|
1563
|
+
});
|
|
1564
|
+
return doc;
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
// src/commands/chat.ts
|
|
1568
|
+
var import_commander5 = require("commander");
|
|
1569
|
+
var import_ora5 = __toESM(require("ora"));
|
|
1570
|
+
var import_inquirer4 = __toESM(require("inquirer"));
|
|
1571
|
+
function createChatCommand() {
|
|
1572
|
+
const chat = new import_commander5.Command("ask").alias("chat").description("Ask the Orin insurance assistant a question");
|
|
1573
|
+
chat.command("query <question...>").alias("q").description("Ask a single question").option("-c, --conversation <id>", "Continue a previous conversation").option("--json", "Output as JSON").action(async (questionParts, options) => {
|
|
1574
|
+
const question = questionParts.join(" ");
|
|
1575
|
+
const client = getClient();
|
|
1576
|
+
const spinner = (0, import_ora5.default)("Thinking...").start();
|
|
1577
|
+
try {
|
|
1578
|
+
const response = await client.chat.ask(question, {
|
|
1579
|
+
conversationId: options.conversation
|
|
1580
|
+
});
|
|
1581
|
+
spinner.succeed("Response received");
|
|
1582
|
+
if (options.json) {
|
|
1583
|
+
json(response);
|
|
1584
|
+
} else {
|
|
1585
|
+
console.log();
|
|
1586
|
+
const cleanResponse = response.response.replace(/<[^>]*>/g, "").replace(/ /g, " ").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
1587
|
+
console.log(cleanResponse);
|
|
1588
|
+
if (response.sources && response.sources.length > 0) {
|
|
1589
|
+
console.log();
|
|
1590
|
+
info(`Sources: ${response.sources.map((s) => s.fileName).join(", ")}`);
|
|
1591
|
+
}
|
|
1592
|
+
console.log();
|
|
1593
|
+
info(`Conversation ID: ${response.conversationId}`);
|
|
1594
|
+
info("Use --conversation flag to continue this conversation");
|
|
1595
|
+
}
|
|
1596
|
+
} catch (err) {
|
|
1597
|
+
spinner.fail("Failed to get response");
|
|
1598
|
+
failWith("API error", err);
|
|
1599
|
+
}
|
|
1600
|
+
});
|
|
1601
|
+
chat.command("session").alias("interactive").alias("i").description("Start an interactive chat session").action(async () => {
|
|
1602
|
+
const client = getClient();
|
|
1603
|
+
let conversationId;
|
|
1604
|
+
header("Orin Insurance Assistant");
|
|
1605
|
+
console.log('Type your questions. Enter "exit" or "quit" to end the session.');
|
|
1606
|
+
console.log();
|
|
1607
|
+
while (true) {
|
|
1608
|
+
const { question } = await import_inquirer4.default.prompt([
|
|
1609
|
+
{
|
|
1610
|
+
type: "input",
|
|
1611
|
+
name: "question",
|
|
1612
|
+
message: "You:",
|
|
1613
|
+
validate: (input) => input.length > 0 || "Please enter a question"
|
|
1614
|
+
}
|
|
1615
|
+
]);
|
|
1616
|
+
const lowerQuestion = question.toLowerCase().trim();
|
|
1617
|
+
if (lowerQuestion === "exit" || lowerQuestion === "quit" || lowerQuestion === "q") {
|
|
1618
|
+
info("Ending session. Goodbye!");
|
|
1619
|
+
break;
|
|
1620
|
+
}
|
|
1621
|
+
const spinner = (0, import_ora5.default)("Thinking...").start();
|
|
1622
|
+
try {
|
|
1623
|
+
const response = await client.chat.ask(question, {
|
|
1624
|
+
conversationId
|
|
1625
|
+
});
|
|
1626
|
+
conversationId = response.conversationId;
|
|
1627
|
+
spinner.stop();
|
|
1628
|
+
console.log();
|
|
1629
|
+
const cleanResponse = response.response.replace(/<[^>]*>/g, "").replace(/ /g, " ").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
1630
|
+
console.log(`Orin: ${cleanResponse}`);
|
|
1631
|
+
console.log();
|
|
1632
|
+
} catch (err) {
|
|
1633
|
+
spinner.fail("Failed to get response");
|
|
1634
|
+
error("Error", err.message);
|
|
1635
|
+
console.log();
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
});
|
|
1639
|
+
chat.arguments("[question...]").action(async (questionParts, options) => {
|
|
1640
|
+
if (!questionParts || questionParts.length === 0) {
|
|
1641
|
+
chat.outputHelp();
|
|
1642
|
+
return;
|
|
1643
|
+
}
|
|
1644
|
+
const question = questionParts.join(" ");
|
|
1645
|
+
const client = getClient();
|
|
1646
|
+
const spinner = (0, import_ora5.default)("Thinking...").start();
|
|
1647
|
+
try {
|
|
1648
|
+
const response = await client.chat.ask(question);
|
|
1649
|
+
spinner.succeed("Response received");
|
|
1650
|
+
console.log();
|
|
1651
|
+
const cleanResponse = response.response.replace(/<[^>]*>/g, "").replace(/ /g, " ").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
1652
|
+
console.log(cleanResponse);
|
|
1653
|
+
if (response.sources && response.sources.length > 0) {
|
|
1654
|
+
console.log();
|
|
1655
|
+
info(`Sources: ${response.sources.map((s) => s.fileName).join(", ")}`);
|
|
1656
|
+
}
|
|
1657
|
+
console.log();
|
|
1658
|
+
info(`Conversation ID: ${response.conversationId}`);
|
|
1659
|
+
} catch (err) {
|
|
1660
|
+
spinner.fail("Failed to get response");
|
|
1661
|
+
failWith("API error", err);
|
|
1662
|
+
}
|
|
1663
|
+
});
|
|
1664
|
+
return chat;
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
// src/commands/quote.ts
|
|
1668
|
+
var import_commander6 = require("commander");
|
|
1669
|
+
var import_ora7 = __toESM(require("ora"));
|
|
1670
|
+
var import_inquirer6 = __toESM(require("inquirer"));
|
|
1671
|
+
var import_sdk5 = require("@delegance/sdk");
|
|
1672
|
+
|
|
1673
|
+
// src/commands/instant-quote.ts
|
|
1674
|
+
var fs5 = __toESM(require("fs"));
|
|
1675
|
+
var import_crypto2 = require("crypto");
|
|
1676
|
+
var import_chalk2 = __toESM(require("chalk"));
|
|
1677
|
+
var import_inquirer5 = __toESM(require("inquirer"));
|
|
1678
|
+
var import_ora6 = __toESM(require("ora"));
|
|
1679
|
+
var import_sdk4 = require("@delegance/sdk");
|
|
1680
|
+
|
|
1681
|
+
// src/commerce/questions.ts
|
|
1682
|
+
var COVERAGE_ALIASES = {
|
|
1683
|
+
gl: "general_liability",
|
|
1684
|
+
"general-liability": "general_liability",
|
|
1685
|
+
wc: "workers_comp",
|
|
1686
|
+
"workers-comp": "workers_comp",
|
|
1687
|
+
bop: "business_owners",
|
|
1688
|
+
cyber: "cyber",
|
|
1689
|
+
pl: "professional_liability",
|
|
1690
|
+
eo: "professional_liability",
|
|
1691
|
+
"e&o": "professional_liability",
|
|
1692
|
+
mpl: "professional_liability",
|
|
1693
|
+
"tech-eo": "tech_eo",
|
|
1694
|
+
auto: "commercial_auto",
|
|
1695
|
+
property: "commercial_property",
|
|
1696
|
+
umbrella: "umbrella",
|
|
1697
|
+
excess: "umbrella"
|
|
1698
|
+
};
|
|
1699
|
+
function parseCoverages(raw) {
|
|
1700
|
+
const ids = raw.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean).map((s) => COVERAGE_ALIASES[s] ?? s.replace(/-/g, "_"));
|
|
1701
|
+
return [...new Set(ids)];
|
|
1702
|
+
}
|
|
1703
|
+
function isVisible(q, answers) {
|
|
1704
|
+
const rule = q.showIf;
|
|
1705
|
+
if (!rule || !rule.questionId || !Array.isArray(rule.equals)) return true;
|
|
1706
|
+
const a = answers[rule.questionId];
|
|
1707
|
+
if (a === void 0 || a === null) return false;
|
|
1708
|
+
return rule.equals.map(String).includes(String(a));
|
|
1709
|
+
}
|
|
1710
|
+
function isRequired(q, answers) {
|
|
1711
|
+
return q.required === true || q.requiredIfShown === true && isVisible(q, answers);
|
|
1712
|
+
}
|
|
1713
|
+
function coerceAnswer(q, raw) {
|
|
1714
|
+
if (raw === void 0 || raw === null || raw === "") return { ok: true, value: void 0 };
|
|
1715
|
+
const text = String(raw).trim();
|
|
1716
|
+
switch (q.type) {
|
|
1717
|
+
case "number":
|
|
1718
|
+
case "currency":
|
|
1719
|
+
case "percentage": {
|
|
1720
|
+
const n = Number(text.replace(/[$,%\s]/g, ""));
|
|
1721
|
+
return Number.isFinite(n) ? { ok: true, value: n } : { ok: false, message: "Enter a number." };
|
|
1722
|
+
}
|
|
1723
|
+
case "yes_no": {
|
|
1724
|
+
const t = text.toLowerCase();
|
|
1725
|
+
if (["y", "yes", "true", "1"].includes(t)) return { ok: true, value: "yes" };
|
|
1726
|
+
if (["n", "no", "false", "0"].includes(t)) return { ok: true, value: "no" };
|
|
1727
|
+
return { ok: false, message: "Answer yes or no." };
|
|
1728
|
+
}
|
|
1729
|
+
case "select": {
|
|
1730
|
+
const opts = q.options ?? [];
|
|
1731
|
+
const hit = opts.find((o) => o.value === text) ?? opts.find((o) => o.label.toLowerCase() === text.toLowerCase());
|
|
1732
|
+
return hit ? { ok: true, value: hit.value } : { ok: false, message: `Choose one of: ${opts.map((o) => o.value).join(", ")}` };
|
|
1733
|
+
}
|
|
1734
|
+
default:
|
|
1735
|
+
return { ok: true, value: text };
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
function validateAnswers(questions, provided) {
|
|
1739
|
+
const answers = {};
|
|
1740
|
+
const invalid = /* @__PURE__ */ new Map();
|
|
1741
|
+
for (const q of questions) {
|
|
1742
|
+
const raw = provided[q.id] ?? q.prefill;
|
|
1743
|
+
const c = coerceAnswer(q, raw);
|
|
1744
|
+
if (!c.ok) invalid.set(q.id, c.message);
|
|
1745
|
+
else if (c.value !== void 0) answers[q.id] = c.value;
|
|
1746
|
+
}
|
|
1747
|
+
const problems = [];
|
|
1748
|
+
for (const q of questions) {
|
|
1749
|
+
if (!isVisible(q, answers)) {
|
|
1750
|
+
delete answers[q.id];
|
|
1751
|
+
continue;
|
|
1752
|
+
}
|
|
1753
|
+
const bad = invalid.get(q.id);
|
|
1754
|
+
if (bad) problems.push(`${q.id}: ${bad}`);
|
|
1755
|
+
else if (isRequired(q, answers) && answers[q.id] === void 0) problems.push(`${q.id}: required ("${q.label}")`);
|
|
1756
|
+
}
|
|
1757
|
+
return { answers, problems };
|
|
1758
|
+
}
|
|
1759
|
+
function sortQuoteRows(quotes) {
|
|
1760
|
+
return quotes.map((q) => ({
|
|
1761
|
+
carrier: q.carrierName || q.carrierId,
|
|
1762
|
+
coverage: q.coverage ?? "",
|
|
1763
|
+
premium: q.premium,
|
|
1764
|
+
status: q.marketStatus || q.state,
|
|
1765
|
+
bindable: q.bindable === true && q.state === "QUOTED",
|
|
1766
|
+
opportunityId: q.opportunityId
|
|
1767
|
+
})).sort((a, b) => {
|
|
1768
|
+
if (a.bindable !== b.bindable) return a.bindable ? -1 : 1;
|
|
1769
|
+
if (a.premium === null && b.premium === null) return 0;
|
|
1770
|
+
if (a.premium === null) return 1;
|
|
1771
|
+
if (b.premium === null) return -1;
|
|
1772
|
+
return a.premium - b.premium;
|
|
1773
|
+
});
|
|
1774
|
+
}
|
|
1775
|
+
function cheapestBindable(rows) {
|
|
1776
|
+
return rows.find((r) => r.bindable && r.premium !== null) ?? null;
|
|
1777
|
+
}
|
|
1778
|
+
function summarizeStatus(s) {
|
|
1779
|
+
const quoted = s.carrierQuotes.filter((q) => q.premium !== null).length;
|
|
1780
|
+
const parts = [`${quoted} of ${s.carrierQuotes.length} carriers priced`];
|
|
1781
|
+
if (s.awaitingBrokerReviewCount > 0) parts.push(`${s.awaitingBrokerReviewCount} with a broker for review`);
|
|
1782
|
+
if (!s.allCarriersResolved) parts.push("still waiting on the rest");
|
|
1783
|
+
return parts.join(", ");
|
|
1784
|
+
}
|
|
1785
|
+
var SUGGESTION_SOURCES = {
|
|
1786
|
+
acord_suggest: "suggested from your business profile"
|
|
1787
|
+
};
|
|
1788
|
+
function knownValue(q) {
|
|
1789
|
+
if (q.prefill !== void 0 && q.prefill !== null && q.prefill !== "") {
|
|
1790
|
+
return { value: q.prefill, kind: "prefill", source: q.prefillSource === "prior_application" ? "your last application" : "your account" };
|
|
1791
|
+
}
|
|
1792
|
+
const s = q.suggested;
|
|
1793
|
+
if (s && s.value !== void 0 && s.value !== null && s.value !== "") {
|
|
1794
|
+
const route = s.route ? String(s.route) : "";
|
|
1795
|
+
return { value: s.value, kind: "suggested", source: SUGGESTION_SOURCES[route] ?? "suggested, please check" };
|
|
1796
|
+
}
|
|
1797
|
+
return null;
|
|
1798
|
+
}
|
|
1799
|
+
function planQuestionRound(questions, answers, handled) {
|
|
1800
|
+
const round = { confirm: [], checklist: [], ask: [] };
|
|
1801
|
+
for (const q of questions) {
|
|
1802
|
+
if (handled.has(q.id) || !isVisible(q, answers)) continue;
|
|
1803
|
+
if (knownValue(q)) round.confirm.push(q);
|
|
1804
|
+
else if (!isRequired(q, answers)) handled.add(q.id);
|
|
1805
|
+
else if (q.type === "yes_no") round.checklist.push(q);
|
|
1806
|
+
else round.ask.push(q);
|
|
1807
|
+
}
|
|
1808
|
+
return round;
|
|
1809
|
+
}
|
|
1810
|
+
function checklistAnswers(items, checkedIds) {
|
|
1811
|
+
const checked = new Set(checkedIds);
|
|
1812
|
+
const out = {};
|
|
1813
|
+
for (const q of items) out[q.id] = checked.has(q.id) ? "yes" : "no";
|
|
1814
|
+
return out;
|
|
1815
|
+
}
|
|
1816
|
+
function formatKnown(q, value) {
|
|
1817
|
+
if (q.type === "yes_no") return String(value).toLowerCase() === "yes" || value === true ? "Yes" : "No";
|
|
1818
|
+
if (q.type === "select") {
|
|
1819
|
+
const hit = (q.options ?? []).find((o) => o.value === String(value));
|
|
1820
|
+
if (hit) return hit.label;
|
|
1821
|
+
}
|
|
1822
|
+
return String(value);
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
// src/commerce/demo.ts
|
|
1826
|
+
var DEMO_BUSINESS = "Riverbend Bakery (demo), Austin TX, 6 employees";
|
|
1827
|
+
var DEMO_QUESTIONS = [
|
|
1828
|
+
{ id: "annual_revenue", label: "Annual revenue (USD)", type: "currency", required: true, prefill: 85e4 },
|
|
1829
|
+
{ id: "employees", label: "Full-time employees", type: "number", required: true, prefill: 6 },
|
|
1830
|
+
{
|
|
1831
|
+
id: "delivery",
|
|
1832
|
+
label: "Do you deliver orders to customers?",
|
|
1833
|
+
type: "yes_no",
|
|
1834
|
+
required: true,
|
|
1835
|
+
prefill: "no"
|
|
1836
|
+
},
|
|
1837
|
+
{
|
|
1838
|
+
id: "delivery_vehicles",
|
|
1839
|
+
label: "How many vehicles make deliveries?",
|
|
1840
|
+
type: "number",
|
|
1841
|
+
required: false,
|
|
1842
|
+
requiredIfShown: true,
|
|
1843
|
+
showIf: { questionId: "delivery", equals: ["yes"] }
|
|
1844
|
+
},
|
|
1845
|
+
{
|
|
1846
|
+
id: "claims_5y",
|
|
1847
|
+
label: "Claims in the last 5 years",
|
|
1848
|
+
type: "select",
|
|
1849
|
+
required: true,
|
|
1850
|
+
options: [
|
|
1851
|
+
{ value: "0", label: "None" },
|
|
1852
|
+
{ value: "1", label: "One" },
|
|
1853
|
+
{ value: "2+", label: "Two or more" }
|
|
1854
|
+
],
|
|
1855
|
+
prefill: "0"
|
|
1856
|
+
}
|
|
1857
|
+
];
|
|
1858
|
+
var DEMO_STATUS = {
|
|
1859
|
+
submissionId: "demo-submission",
|
|
1860
|
+
coverages: ["general_liability", "workers_comp"],
|
|
1861
|
+
expiresAt: null,
|
|
1862
|
+
submitted: true,
|
|
1863
|
+
submitStatus: "completed",
|
|
1864
|
+
allCarriersResolved: true,
|
|
1865
|
+
awaitingBrokerReviewCount: 0,
|
|
1866
|
+
carrierQuotes: [
|
|
1867
|
+
{ opportunityId: "demo-1", carrierId: "COTERIE", carrierName: "Coterie", coverage: "CGL", state: "QUOTED", marketStatus: "quote", premium: 612, quoteNumber: "DEMO-GL-1", bindable: true },
|
|
1868
|
+
{ opportunityId: "demo-2", carrierId: "CHUBB", carrierName: "Chubb", coverage: "CGL", state: "QUOTED", marketStatus: "quote", premium: 748, quoteNumber: "DEMO-GL-2", bindable: true },
|
|
1869
|
+
{ opportunityId: "demo-3", carrierId: "PIE_INSURANCE", carrierName: "Pie Insurance", coverage: "WC", state: "QUOTED", marketStatus: "quote", premium: 1184, quoteNumber: "DEMO-WC-1", bindable: true },
|
|
1870
|
+
{ opportunityId: "demo-4", carrierId: "AMTRUST", carrierName: "AmTrust", coverage: "WC", state: "DECLINED", marketStatus: "declined", premium: null, quoteNumber: null, bindable: false }
|
|
1871
|
+
]
|
|
1872
|
+
};
|
|
1873
|
+
|
|
1874
|
+
// src/commands/instant-quote.ts
|
|
1875
|
+
function activeRequestLines(err, coverageFlag) {
|
|
1876
|
+
if (!(err instanceof import_sdk4.DeleganceError) || err.statusCode !== 409 || !err.body) return null;
|
|
1877
|
+
const { body } = err;
|
|
1878
|
+
const code = body.message;
|
|
1879
|
+
const requote = `delegance quote start --coverage ${coverageFlag} --replace`;
|
|
1880
|
+
if (code === "active_request_exists") {
|
|
1881
|
+
const active = Array.isArray(body.active) ? body.active : [];
|
|
1882
|
+
const first = active[0];
|
|
1883
|
+
const id = typeof first?.submissionId === "string" ? first.submissionId : null;
|
|
1884
|
+
const carriers = Array.isArray(first?.carriers) ? first.carriers.map((c) => `${c.carrierId}${c.state ? ` (${c.state.toLowerCase()})` : ""}`).join(", ") : "";
|
|
1885
|
+
return [
|
|
1886
|
+
`Already with carriers${id ? `: ${id}` : ""}${carriers ? `, ${carriers}` : ""}.`,
|
|
1887
|
+
`Check it: ${id ? `delegance quote status ${id}` : "delegance quote status <id>"} Replace it: ${requote}`
|
|
1888
|
+
];
|
|
1889
|
+
}
|
|
1890
|
+
if (code === "replace_conflict") {
|
|
1891
|
+
const carrier = typeof body.carrier === "string" ? body.carrier : "A carrier";
|
|
1892
|
+
return [
|
|
1893
|
+
`Cannot replace: ${carrier} has a bind in progress or bound on the earlier request.`,
|
|
1894
|
+
"Reply to your quote confirmation email and a broker will sort it out."
|
|
1895
|
+
];
|
|
1896
|
+
}
|
|
1897
|
+
if (code === "answers_changed_requote_required") {
|
|
1898
|
+
return ["Carriers are quoting your earlier answers.", `Re-quote with the new answers: ${requote}`];
|
|
1899
|
+
}
|
|
1900
|
+
return null;
|
|
1901
|
+
}
|
|
1902
|
+
function answerMode(o) {
|
|
1903
|
+
if (o.hasAnswersFile) return "file";
|
|
1904
|
+
if (o.demo) return o.json || !o.isTTY ? "suggested" : "prompt";
|
|
1905
|
+
if (o.json) return "print_questions";
|
|
1906
|
+
return o.isTTY ? "prompt" : "needs_answers";
|
|
1907
|
+
}
|
|
1908
|
+
function suggestedAnswers(questions) {
|
|
1909
|
+
const answers = {};
|
|
1910
|
+
for (const q of questions) {
|
|
1911
|
+
if (!isVisible(q, answers) || q.prefill === void 0 || q.prefill === null) continue;
|
|
1912
|
+
const c = coerceAnswer(q, String(q.prefill));
|
|
1913
|
+
if (c.ok && c.value !== void 0) answers[q.id] = c.value;
|
|
1914
|
+
}
|
|
1915
|
+
return answers;
|
|
1916
|
+
}
|
|
1917
|
+
var isInteractive = () => Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
1918
|
+
async function askOne(q, answers, fallback) {
|
|
1919
|
+
const required = isRequired(q, answers);
|
|
1920
|
+
const label = `${q.label}${required ? "" : import_chalk2.default.dim(" (optional)")}`;
|
|
1921
|
+
let prompt;
|
|
1922
|
+
if (q.type === "yes_no") {
|
|
1923
|
+
prompt = { type: "list", name: "v", message: label, choices: [{ name: "Yes", value: "yes" }, { name: "No", value: "no" }], default: fallback };
|
|
1924
|
+
} else if (q.type === "select" && q.options?.length) {
|
|
1925
|
+
prompt = { type: "list", name: "v", message: label, choices: q.options.map((o) => ({ name: o.label, value: o.value })), default: fallback };
|
|
1926
|
+
} else {
|
|
1927
|
+
prompt = {
|
|
1928
|
+
type: "input",
|
|
1929
|
+
name: "v",
|
|
1930
|
+
message: label,
|
|
1931
|
+
default: fallback === void 0 || fallback === null ? void 0 : String(fallback),
|
|
1932
|
+
validate: (v2) => {
|
|
1933
|
+
if (!v2 && required) return "Required";
|
|
1934
|
+
const c2 = coerceAnswer(q, v2);
|
|
1935
|
+
return c2.ok || c2.message;
|
|
1936
|
+
}
|
|
1937
|
+
};
|
|
1938
|
+
}
|
|
1939
|
+
const { v } = await import_inquirer5.default.prompt([prompt]);
|
|
1940
|
+
const c = coerceAnswer(q, v);
|
|
1941
|
+
if (c.ok && c.value !== void 0) answers[q.id] = c.value;
|
|
1942
|
+
}
|
|
1943
|
+
async function askQuestions(questions, answers = {}) {
|
|
1944
|
+
const handled = /* @__PURE__ */ new Set();
|
|
1945
|
+
const stats = { asked: 0, checklist: 0, prefilled: 0, suggestedConfirmed: 0, suggestedChanged: 0 };
|
|
1946
|
+
for (; ; ) {
|
|
1947
|
+
const round = planQuestionRound(questions, answers, handled);
|
|
1948
|
+
if (round.confirm.length + round.checklist.length + round.ask.length === 0) break;
|
|
1949
|
+
if (round.confirm.length) {
|
|
1950
|
+
const { keep } = await import_inquirer5.default.prompt([
|
|
1951
|
+
{
|
|
1952
|
+
type: "checkbox",
|
|
1953
|
+
name: "keep",
|
|
1954
|
+
message: "We already have these answers. Uncheck any that are wrong, then press Enter:",
|
|
1955
|
+
pageSize: 20,
|
|
1956
|
+
choices: round.confirm.map((q) => {
|
|
1957
|
+
const k = knownValue(q);
|
|
1958
|
+
return { name: `${q.label}: ${import_chalk2.default.bold(formatKnown(q, k.value))} ${import_chalk2.default.dim(`(${k.source})`)}`, value: q.id, checked: true };
|
|
1959
|
+
})
|
|
1960
|
+
}
|
|
1961
|
+
]);
|
|
1962
|
+
const kept = new Set(keep);
|
|
1963
|
+
for (const q of round.confirm) {
|
|
1964
|
+
handled.add(q.id);
|
|
1965
|
+
const k = knownValue(q);
|
|
1966
|
+
const kc = kept.has(q.id) ? coerceAnswer(q, k.value) : null;
|
|
1967
|
+
if (kc && kc.ok && kc.value !== void 0) {
|
|
1968
|
+
answers[q.id] = kc.value;
|
|
1969
|
+
if (k.kind === "prefill") stats.prefilled += 1;
|
|
1970
|
+
else stats.suggestedConfirmed += 1;
|
|
1971
|
+
} else {
|
|
1972
|
+
if (k.kind === "suggested") stats.suggestedChanged += 1;
|
|
1973
|
+
stats.asked += 1;
|
|
1974
|
+
await askOne(q, answers);
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
if (round.checklist.length) {
|
|
1979
|
+
const { yes } = await import_inquirer5.default.prompt([
|
|
1980
|
+
{
|
|
1981
|
+
type: "checkbox",
|
|
1982
|
+
name: "yes",
|
|
1983
|
+
message: `Check every statement that is TRUE for your business (${round.checklist.length}). Unchecked means No:`,
|
|
1984
|
+
pageSize: 20,
|
|
1985
|
+
choices: round.checklist.map((q) => ({ name: q.label, value: q.id }))
|
|
1986
|
+
}
|
|
1987
|
+
]);
|
|
1988
|
+
const yn = checklistAnswers(round.checklist, yes);
|
|
1989
|
+
const yesCount = Object.values(yn).filter((v) => v === "yes").length;
|
|
1990
|
+
const { ok } = await import_inquirer5.default.prompt([
|
|
1991
|
+
{ type: "confirm", name: "ok", message: `Yes to ${yesCount}, No to ${round.checklist.length - yesCount}. Correct?`, default: true }
|
|
1992
|
+
]);
|
|
1993
|
+
if (ok) {
|
|
1994
|
+
Object.assign(answers, yn);
|
|
1995
|
+
round.checklist.forEach((q) => handled.add(q.id));
|
|
1996
|
+
stats.checklist += round.checklist.length;
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
for (const q of round.ask) {
|
|
2000
|
+
handled.add(q.id);
|
|
2001
|
+
stats.asked += 1;
|
|
2002
|
+
await askOne(q, answers);
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
return { answers, stats };
|
|
2006
|
+
}
|
|
2007
|
+
function handoffLines(r) {
|
|
2008
|
+
const lines = [
|
|
2009
|
+
r.alreadyHandedOff ? "A Delegance broker already has this request." : "A Delegance broker has this request and will place it with a market that quotes by hand."
|
|
2010
|
+
];
|
|
2011
|
+
if (!r.alreadyHandedOff && r.unansweredRequired > 0) {
|
|
2012
|
+
lines.push(`The broker will email you for the ${r.unansweredRequired} answer(s) still missing.`);
|
|
2013
|
+
}
|
|
2014
|
+
lines.push(`Check it: delegance quote status ${r.submissionId}`);
|
|
2015
|
+
return lines;
|
|
2016
|
+
}
|
|
2017
|
+
async function runBrokerHandoff(submissionId, instantAnswers, opts) {
|
|
2018
|
+
const client = getClient();
|
|
2019
|
+
const say = (l) => opts.json ? console.error(l) : info(l);
|
|
2020
|
+
let plan;
|
|
2021
|
+
try {
|
|
2022
|
+
plan = await client.instantQuotes.brokerHandoffQuestions(submissionId, instantAnswers);
|
|
2023
|
+
} catch (err) {
|
|
2024
|
+
if (err instanceof import_sdk4.DeleganceError && err.statusCode === 503) {
|
|
2025
|
+
say("A broker can still market it: reply to the confirmation email, or start in the portal.");
|
|
2026
|
+
return;
|
|
2027
|
+
}
|
|
2028
|
+
failWith("Could not load the broker questions", err);
|
|
2029
|
+
}
|
|
2030
|
+
if (plan.alreadyHandedOff) {
|
|
2031
|
+
const lines = handoffLines({ submissionId, unansweredRequired: 0, alreadyHandedOff: true });
|
|
2032
|
+
if (opts.json) json({ submissionId, status: "broker_review", alreadyHandedOff: true, brokerTaskId: plan.brokerTaskId ?? null });
|
|
2033
|
+
else lines.forEach((l) => info(l));
|
|
2034
|
+
return;
|
|
2035
|
+
}
|
|
2036
|
+
let answers = {};
|
|
2037
|
+
let complete = true;
|
|
2038
|
+
const idempotencyKey = (0, import_crypto2.randomUUID)();
|
|
2039
|
+
let inFlight = null;
|
|
2040
|
+
if (opts.answersFile) {
|
|
2041
|
+
try {
|
|
2042
|
+
const provided = JSON.parse(fs5.readFileSync(opts.answersFile, "utf8"));
|
|
2043
|
+
const checked = validateAnswers(plan.questions, provided);
|
|
2044
|
+
if (checked.problems.length) {
|
|
2045
|
+
error("Some answers need fixing:");
|
|
2046
|
+
checked.problems.forEach((p) => console.log(` - ${p}`));
|
|
2047
|
+
process.exit(1);
|
|
2048
|
+
}
|
|
2049
|
+
answers = checked.answers;
|
|
2050
|
+
} catch (e) {
|
|
2051
|
+
error(`Could not read ${opts.answersFile}: ${e.message}`);
|
|
2052
|
+
process.exit(1);
|
|
2053
|
+
}
|
|
2054
|
+
} else if (!opts.json && isInteractive() && plan.questions.length > 0) {
|
|
2055
|
+
info(`A broker can place ${plan.coverages.join(", ")} with a market that quotes by hand. ${plan.questions.length} application question(s) left.`);
|
|
2056
|
+
info("Press Ctrl-C to stop at any point; what you have answered goes to the broker.");
|
|
2057
|
+
const onInterrupt = () => {
|
|
2058
|
+
complete = false;
|
|
2059
|
+
void send().then(() => process.exit(0));
|
|
2060
|
+
};
|
|
2061
|
+
process.once("SIGINT", onInterrupt);
|
|
2062
|
+
try {
|
|
2063
|
+
await askQuestions(plan.questions, answers);
|
|
2064
|
+
} catch {
|
|
2065
|
+
complete = false;
|
|
2066
|
+
} finally {
|
|
2067
|
+
process.removeListener("SIGINT", onInterrupt);
|
|
2068
|
+
}
|
|
2069
|
+
} else {
|
|
2070
|
+
complete = plan.questions.length === 0;
|
|
2071
|
+
}
|
|
2072
|
+
await send();
|
|
2073
|
+
function send() {
|
|
2074
|
+
inFlight ?? (inFlight = sendHandoff(submissionId, { answers, instantAnswers, complete }, idempotencyKey, opts.json));
|
|
2075
|
+
return inFlight;
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
async function sendHandoff(submissionId, input, idempotencyKey, asJson) {
|
|
2079
|
+
const spinner = asJson ? null : (0, import_ora6.default)("Sending it to a broker...").start();
|
|
2080
|
+
try {
|
|
2081
|
+
const result = await getClient().instantQuotes.brokerHandoff(submissionId, input, { idempotencyKey });
|
|
2082
|
+
spinner?.succeed("Sent to a broker.");
|
|
2083
|
+
if (asJson) json(result);
|
|
2084
|
+
else handoffLines(result).forEach((l) => info(l));
|
|
2085
|
+
} catch (err) {
|
|
2086
|
+
spinner?.fail("Could not reach a broker");
|
|
2087
|
+
console.error(`Try again: delegance quote handoff ${submissionId}`);
|
|
2088
|
+
failWith("API error", err);
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
function printFloorDrops(failures) {
|
|
2092
|
+
const floors = (failures ?? []).filter((f) => f.reason.startsWith("requirement_floor:") && f.missing);
|
|
2093
|
+
if (floors.length === 0) return;
|
|
2094
|
+
warn("Some carriers were skipped because a required fact is missing:");
|
|
2095
|
+
floors.forEach((f) => console.log(` - ${f.carrierId}: ${f.missing}`));
|
|
2096
|
+
info("Add it to your business profile in the portal, then run the quote again.");
|
|
2097
|
+
}
|
|
2098
|
+
function printQuoteTable(status, opts = {}) {
|
|
2099
|
+
const rows = sortQuoteRows(status.carrierQuotes);
|
|
2100
|
+
if (rows.length === 0) {
|
|
2101
|
+
if (status.brokerReview) {
|
|
2102
|
+
info(`A broker has this request (${status.brokerReview.status.replace(/_/g, " ")}). They place it by hand and email you with quotes or questions.`);
|
|
2103
|
+
return;
|
|
2104
|
+
}
|
|
2105
|
+
warn("No carrier offers yet.");
|
|
2106
|
+
if (status.awaitingBrokerReviewCount > 0) info(`${status.awaitingBrokerReviewCount} quote(s) are with a broker for review.`);
|
|
2107
|
+
return;
|
|
2108
|
+
}
|
|
2109
|
+
const best = cheapestBindable(rows);
|
|
2110
|
+
if (best) {
|
|
2111
|
+
console.log(import_chalk2.default.bold(`${opts.demo ? "[DEMO] " : ""}Cheapest bindable: ${best.carrier} ${best.coverage} at ${formatCurrency(best.premium)}/yr`));
|
|
2112
|
+
}
|
|
2113
|
+
table(
|
|
2114
|
+
["Carrier", "Coverage", "Premium/yr", "Status", "Bindable", "Offer id"],
|
|
2115
|
+
rows.map((r) => {
|
|
2116
|
+
const cells = [r.carrier, r.coverage, r.premium === null ? "-" : formatCurrency(r.premium), r.status, r.bindable ? "yes" : "no", r.opportunityId];
|
|
2117
|
+
return r === best ? cells.map((c) => import_chalk2.default.bold(String(c))) : cells;
|
|
2118
|
+
})
|
|
2119
|
+
);
|
|
2120
|
+
console.log(import_chalk2.default.dim(summarizeStatus(status)));
|
|
2121
|
+
if (best && !opts.demo) {
|
|
2122
|
+
console.log(import_chalk2.default.green(`Bind it: delegance bind ${status.submissionId} ${best.opportunityId}`));
|
|
2123
|
+
}
|
|
2124
|
+
if (opts.demo) console.log(import_chalk2.default.yellow('DEMO: sample premiums, nothing was sent to a carrier. Run "delegance signup" for real quotes.'));
|
|
2125
|
+
}
|
|
2126
|
+
async function runDemo(asJson, answersFile) {
|
|
2127
|
+
(asJson ? console.error : console.log)(import_chalk2.default.yellow(`DEMO quote for ${DEMO_BUSINESS}. Nothing leaves this machine.`));
|
|
2128
|
+
if (answersFile) {
|
|
2129
|
+
let provided;
|
|
2130
|
+
try {
|
|
2131
|
+
provided = JSON.parse(fs5.readFileSync(answersFile, "utf8"));
|
|
2132
|
+
} catch (e) {
|
|
2133
|
+
error(`Could not read ${answersFile}: ${e.message}`);
|
|
2134
|
+
process.exit(1);
|
|
2135
|
+
}
|
|
2136
|
+
const { problems } = validateAnswers(DEMO_QUESTIONS, provided);
|
|
2137
|
+
if (problems.length) {
|
|
2138
|
+
problems.forEach((p) => error(p));
|
|
2139
|
+
process.exit(1);
|
|
2140
|
+
}
|
|
2141
|
+
} else {
|
|
2142
|
+
const mode = answerMode({ demo: true, hasAnswersFile: false, json: asJson, isTTY: isInteractive() });
|
|
2143
|
+
if (mode === "prompt") await askQuestions(DEMO_QUESTIONS);
|
|
2144
|
+
else {
|
|
2145
|
+
suggestedAnswers(DEMO_QUESTIONS);
|
|
2146
|
+
if (!asJson) info("No terminal to ask in, so the demo uses its suggested answers.");
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
const spinner = asJson ? null : (0, import_ora6.default)("Asking carriers (demo)...").start();
|
|
2150
|
+
await new Promise((resolve) => setTimeout(resolve, asJson ? 0 : 1200));
|
|
2151
|
+
spinner?.succeed("4 carriers answered (demo)");
|
|
2152
|
+
if (asJson) json({ demo: true, ...DEMO_STATUS });
|
|
2153
|
+
else printQuoteTable(DEMO_STATUS, { demo: true });
|
|
2154
|
+
}
|
|
2155
|
+
function addInstantQuoteCommands(quote) {
|
|
2156
|
+
quote.command("start").description("Get instant quotes from real carriers: answers the questions, files them, waits for premiums").option("-c, --coverage <list>", "Coverages, comma-separated: gl, wc, bop, cyber, pl (E&O), auto, property, umbrella", "gl").option("-a, --answers <file>", "JSON file of answers keyed by question id (skips the prompts)").option("--demo", "Walk the flow on sample data; nothing is sent anywhere").option("--no-wait", 'File the quote and exit; check later with "quote status"').option("--timeout <minutes>", "How long to wait for premiums", "10").option("--replace", "Close the request already with carriers for this coverage and re-quote").option("--json", "Output as JSON").action(async (options) => {
|
|
2157
|
+
if (options.demo) {
|
|
2158
|
+
await runDemo(!!options.json, options.answers);
|
|
2159
|
+
return;
|
|
2160
|
+
}
|
|
2161
|
+
const coverages = parseCoverages(String(options.coverage));
|
|
2162
|
+
if (coverages.length === 0) {
|
|
2163
|
+
error("Name at least one coverage with --coverage");
|
|
2164
|
+
process.exit(1);
|
|
2165
|
+
}
|
|
2166
|
+
const client = getClient();
|
|
2167
|
+
const spinner = (0, import_ora6.default)("Checking which carriers can quote...").start();
|
|
2168
|
+
let session;
|
|
2169
|
+
try {
|
|
2170
|
+
session = await client.instantQuotes.start(coverages, options.replace ? { fresh: true } : {});
|
|
2171
|
+
spinner.succeed(
|
|
2172
|
+
`${session.eligibleCarriers.length} carrier(s) can quote: ${session.eligibleCarriers.map((c) => c.name).join(", ") || "broker review only"}`
|
|
2173
|
+
);
|
|
2174
|
+
} catch (err) {
|
|
2175
|
+
spinner.fail("Could not start the quote");
|
|
2176
|
+
failWith("API error", err);
|
|
2177
|
+
}
|
|
2178
|
+
let answers;
|
|
2179
|
+
if (options.answers) {
|
|
2180
|
+
let provided;
|
|
2181
|
+
try {
|
|
2182
|
+
provided = JSON.parse(fs5.readFileSync(options.answers, "utf8"));
|
|
2183
|
+
} catch (e) {
|
|
2184
|
+
error(`Could not read ${options.answers}: ${e.message}`);
|
|
2185
|
+
process.exit(1);
|
|
2186
|
+
}
|
|
2187
|
+
const checked = validateAnswers(session.questions, provided);
|
|
2188
|
+
if (checked.problems.length) {
|
|
2189
|
+
error("Some answers need fixing:");
|
|
2190
|
+
checked.problems.forEach((p) => console.log(` - ${p}`));
|
|
2191
|
+
info(`Question ids: ${session.questions.map((q) => q.id).join(", ")}`);
|
|
2192
|
+
process.exit(1);
|
|
2193
|
+
}
|
|
2194
|
+
answers = checked.answers;
|
|
2195
|
+
} else if (answerMode({ demo: false, hasAnswersFile: false, json: !!options.json, isTTY: isInteractive() }) === "needs_answers") {
|
|
2196
|
+
error("No terminal to ask the questions in.");
|
|
2197
|
+
info(`Re-run with --answers <file.json>, or with --json to print the questions. Session: ${session.submissionId}`);
|
|
2198
|
+
process.exit(1);
|
|
2199
|
+
} else if (options.json) {
|
|
2200
|
+
json({ submissionId: session.submissionId, sessionVersion: session.sessionVersion, questions: session.questions });
|
|
2201
|
+
return;
|
|
2202
|
+
} else {
|
|
2203
|
+
const asked = await askQuestions(session.questions);
|
|
2204
|
+
answers = asked.answers;
|
|
2205
|
+
const s = asked.stats;
|
|
2206
|
+
info(`${s.prefilled + s.suggestedConfirmed} answer(s) confirmed from what we have, ${s.checklist} on the checklist, ${s.asked} asked.`);
|
|
2207
|
+
}
|
|
2208
|
+
const submitSpinner = (0, import_ora6.default)("Sending your answers to the carriers...").start();
|
|
2209
|
+
let submitted;
|
|
2210
|
+
try {
|
|
2211
|
+
submitted = await client.instantQuotes.submit({
|
|
2212
|
+
submissionId: session.submissionId,
|
|
2213
|
+
sessionVersion: session.sessionVersion,
|
|
2214
|
+
answers,
|
|
2215
|
+
...options.replace ? { replace: true } : {}
|
|
2216
|
+
});
|
|
2217
|
+
} catch (err) {
|
|
2218
|
+
const guardLines = activeRequestLines(err, String(options.coverage));
|
|
2219
|
+
if (guardLines) {
|
|
2220
|
+
submitSpinner.fail("Nothing was sent.");
|
|
2221
|
+
guardLines.forEach((l) => options.json ? console.error(l) : info(l));
|
|
2222
|
+
process.exit(1);
|
|
2223
|
+
}
|
|
2224
|
+
submitSpinner.fail("Submit failed");
|
|
2225
|
+
console.error(`Before trying again, check whether it went through: delegance quote status ${session.submissionId}`);
|
|
2226
|
+
failWith("API error", err);
|
|
2227
|
+
}
|
|
2228
|
+
if (submitted.status === "no_carriers") {
|
|
2229
|
+
if (options.json) console.error("No carrier could take this one instantly. Sending it to a broker.");
|
|
2230
|
+
else submitSpinner.warn("No carrier could take this one instantly.");
|
|
2231
|
+
if (!options.json) printFloorDrops(submitted.partialFailures);
|
|
2232
|
+
await runBrokerHandoff(session.submissionId, answers, { json: !!options.json });
|
|
2233
|
+
return;
|
|
2234
|
+
}
|
|
2235
|
+
if (options.json && (submitted.status !== "submitted" || options.wait === false)) {
|
|
2236
|
+
json({ submissionId: session.submissionId, ...submitted });
|
|
2237
|
+
return;
|
|
2238
|
+
}
|
|
2239
|
+
if (submitted.status === "acord_pending") {
|
|
2240
|
+
submitSpinner.succeed("Sent to a broker: these coverages need a person, not an instant quote.");
|
|
2241
|
+
info(`Track it with: delegance quote status ${session.submissionId}`);
|
|
2242
|
+
return;
|
|
2243
|
+
}
|
|
2244
|
+
submitSpinner.succeed(`Filed with ${submitted.quotes.filter((q) => q.status === "submitted").length} carrier(s).`);
|
|
2245
|
+
printFloorDrops(submitted.partialFailures);
|
|
2246
|
+
if (submitted.replaceNotice && !options.json) info(submitted.replaceNotice);
|
|
2247
|
+
if (options.wait === false) {
|
|
2248
|
+
info(`Check premiums with: delegance quote status ${session.submissionId} --wait`);
|
|
2249
|
+
return;
|
|
2250
|
+
}
|
|
2251
|
+
await waitAndPrint(session.submissionId, Number(options.timeout) * 6e4 || 6e5, !!options.json);
|
|
2252
|
+
});
|
|
2253
|
+
quote.command("submit <submissionId>").description('File answers for a session started with "quote start --json" (scripted flow)').requiredOption("--session-version <version>", 'sessionVersion from "quote start --json"').requiredOption("-a, --answers <file>", "JSON file of answers keyed by question id").option("--idempotency-key <key>", "Reuse on a retry so the same submission is not filed twice").option("--replace", "Close the request already with carriers for this coverage and re-quote").option("--no-wait", "Exit after filing").option("--timeout <minutes>", "How long to wait for premiums", "10").option("--json", "Output as JSON").action(async (submissionId, options) => {
|
|
2254
|
+
let answers;
|
|
2255
|
+
try {
|
|
2256
|
+
answers = JSON.parse(fs5.readFileSync(options.answers, "utf8"));
|
|
2257
|
+
} catch (e) {
|
|
2258
|
+
error(`Could not read ${options.answers}: ${e.message}`);
|
|
2259
|
+
process.exit(1);
|
|
2260
|
+
}
|
|
2261
|
+
try {
|
|
2262
|
+
const submitted = await getClient().instantQuotes.submit(
|
|
2263
|
+
{ submissionId, sessionVersion: options.sessionVersion, answers, ...options.replace ? { replace: true } : {} },
|
|
2264
|
+
options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}
|
|
2265
|
+
);
|
|
2266
|
+
if (submitted.status === "no_carriers") {
|
|
2267
|
+
(options.json ? console.error : info)("No carrier could take this one instantly. Sending it to a broker.");
|
|
2268
|
+
await runBrokerHandoff(submissionId, answers, { json: !!options.json });
|
|
2269
|
+
return;
|
|
2270
|
+
}
|
|
2271
|
+
if (options.json && (submitted.status !== "submitted" || options.wait === false)) {
|
|
2272
|
+
json({ submissionId, ...submitted });
|
|
2273
|
+
return;
|
|
2274
|
+
}
|
|
2275
|
+
if (submitted.status !== "submitted") {
|
|
2276
|
+
info("Sent to a broker for these coverages.");
|
|
2277
|
+
info(`Check it: delegance quote status ${submissionId}`);
|
|
2278
|
+
return;
|
|
2279
|
+
}
|
|
2280
|
+
if (options.wait === false) {
|
|
2281
|
+
info(`Check premiums with: delegance quote status ${submissionId} --wait`);
|
|
2282
|
+
return;
|
|
2283
|
+
}
|
|
2284
|
+
await waitAndPrint(submissionId, Number(options.timeout) * 6e4 || 6e5, !!options.json);
|
|
2285
|
+
} catch (err) {
|
|
2286
|
+
const guardLines = activeRequestLines(err, "<list>");
|
|
2287
|
+
if (guardLines) {
|
|
2288
|
+
error("Nothing was sent.");
|
|
2289
|
+
guardLines.forEach((l) => options.json ? console.error(l) : info(l));
|
|
2290
|
+
process.exit(1);
|
|
2291
|
+
}
|
|
2292
|
+
console.error(`Before trying again, check whether it went through: delegance quote status ${submissionId}`);
|
|
2293
|
+
failWith("Submit failed", err);
|
|
2294
|
+
}
|
|
2295
|
+
});
|
|
2296
|
+
quote.command("handoff <submissionId>").description("Send a quote no carrier could take instantly to a broker, after the application questions they need").option("-a, --answers <file>", "JSON file of answers keyed by question id (skips the prompts)").option("--json", "Output as JSON").action(async (submissionId, options) => {
|
|
2297
|
+
await runBrokerHandoff(submissionId, {}, { json: !!options.json, answersFile: options.answers });
|
|
2298
|
+
});
|
|
2299
|
+
quote.command("status <submissionId>").description("Premiums and bindable offers for an instant quote").option("--wait", "Poll until every carrier answers").option("--timeout <minutes>", "How long to wait", "10").option("--json", "Output as JSON").action(async (submissionId, options) => {
|
|
2300
|
+
if (options.wait) {
|
|
2301
|
+
await waitAndPrint(submissionId, Number(options.timeout) * 6e4 || 6e5, !!options.json);
|
|
2302
|
+
return;
|
|
2303
|
+
}
|
|
2304
|
+
try {
|
|
2305
|
+
const status = await getClient().instantQuotes.status(submissionId);
|
|
2306
|
+
if (options.json) json(status);
|
|
2307
|
+
else printQuoteTable(status);
|
|
2308
|
+
} catch (err) {
|
|
2309
|
+
failWith("API error", err);
|
|
2310
|
+
}
|
|
2311
|
+
});
|
|
2312
|
+
}
|
|
2313
|
+
async function waitAndPrint(submissionId, timeoutMs, asJson) {
|
|
2314
|
+
const client = getClient();
|
|
2315
|
+
const started = Date.now();
|
|
2316
|
+
const spinner = asJson ? null : (0, import_ora6.default)("Waiting for carriers to price...").start();
|
|
2317
|
+
try {
|
|
2318
|
+
const status = await client.instantQuotes.waitForQuotes(submissionId, {
|
|
2319
|
+
timeoutMs,
|
|
2320
|
+
intervalMs: 1e4,
|
|
2321
|
+
onPoll: (s) => {
|
|
2322
|
+
if (spinner) spinner.text = `Waiting for carriers (${Math.round((Date.now() - started) / 1e3)}s): ${summarizeStatus(s)}`;
|
|
2323
|
+
}
|
|
2324
|
+
});
|
|
2325
|
+
spinner?.stop();
|
|
2326
|
+
if (asJson) json(status);
|
|
2327
|
+
else printQuoteTable(status);
|
|
2328
|
+
if (!status.allCarriersResolved && !asJson) info(`Some carriers are still working. Check again: delegance quote status ${submissionId} --wait`);
|
|
2329
|
+
} catch (err) {
|
|
2330
|
+
spinner?.fail("Could not read the quote");
|
|
2331
|
+
failWith("API error", err);
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
// src/commands/quote.ts
|
|
2336
|
+
var QUOTE_SUBMISSION_UNAVAILABLE = "Quote submission via API isn't available yet";
|
|
2337
|
+
function createQuoteCommand() {
|
|
2338
|
+
const quote = new import_commander6.Command("quote").alias("quotes").description("Request and manage insurance quotes");
|
|
2339
|
+
quote.command("carriers").description("List available carriers for quoting").option("--json", "Output as JSON").action(async (options) => {
|
|
2340
|
+
const client = getClient();
|
|
2341
|
+
const spinner = (0, import_ora7.default)("Fetching carriers...").start();
|
|
2342
|
+
try {
|
|
2343
|
+
const { carriers, count } = await client.quotes.listCarriers();
|
|
2344
|
+
spinner.succeed(`Found ${count} carriers`);
|
|
2345
|
+
if (carriers.length === 0) {
|
|
2346
|
+
info("No carriers available");
|
|
2347
|
+
return;
|
|
2348
|
+
}
|
|
2349
|
+
if (options.json) {
|
|
2350
|
+
json(carriers);
|
|
2351
|
+
} else {
|
|
2352
|
+
console.log();
|
|
2353
|
+
table(
|
|
2354
|
+
["ID", "Name", "Avg Quote Time", "Queue Depth"],
|
|
2355
|
+
carriers.map((c) => [
|
|
2356
|
+
c.id,
|
|
2357
|
+
c.name,
|
|
2358
|
+
`${Math.round(c.averageQuoteTimeSeconds / 60)} min`,
|
|
2359
|
+
String(c.queueDepth)
|
|
2360
|
+
])
|
|
2361
|
+
);
|
|
2362
|
+
}
|
|
2363
|
+
} catch (err) {
|
|
2364
|
+
spinner.fail("Failed to fetch carriers");
|
|
2365
|
+
failWith("API error", err);
|
|
2366
|
+
}
|
|
2367
|
+
});
|
|
2368
|
+
quote.command("request").description('Legacy partner queue submission (not live). For an instant quote, use "quote start"').option("-c, --carriers <carriers...>", "Carrier IDs to quote (space-separated)").option("-p, --priority <priority>", "Priority level (low, normal, high)", "normal").option("--all", "Request quotes from all available carriers").option("-i, --interactive", "Interactive carrier selection").option("--json", "Output as JSON").action(async (options) => {
|
|
2369
|
+
const client = getClient();
|
|
2370
|
+
let carrierIds = options.carriers || [];
|
|
2371
|
+
const carriersSpinner = (0, import_ora7.default)("Fetching available carriers...").start();
|
|
2372
|
+
let availableCarriers;
|
|
2373
|
+
try {
|
|
2374
|
+
({ carriers: availableCarriers } = await client.quotes.listCarriers());
|
|
2375
|
+
carriersSpinner.stop();
|
|
2376
|
+
} catch (err) {
|
|
2377
|
+
carriersSpinner.fail("Failed to fetch carriers");
|
|
2378
|
+
failWith("API error", err);
|
|
2379
|
+
}
|
|
2380
|
+
if (availableCarriers.length === 0) {
|
|
2381
|
+
error("No carriers available for quoting");
|
|
2382
|
+
process.exit(1);
|
|
2383
|
+
}
|
|
2384
|
+
if (options.interactive || carrierIds.length === 0 && !options.all) {
|
|
2385
|
+
const { selectedCarriers } = await import_inquirer6.default.prompt([
|
|
2386
|
+
{
|
|
2387
|
+
type: "checkbox",
|
|
2388
|
+
name: "selectedCarriers",
|
|
2389
|
+
message: "Select carriers to quote:",
|
|
2390
|
+
choices: availableCarriers.map((c) => ({
|
|
2391
|
+
name: `${c.name} (avg ${Math.round(c.averageQuoteTimeSeconds / 60)} min)`,
|
|
2392
|
+
value: c.id,
|
|
2393
|
+
checked: false
|
|
2394
|
+
})),
|
|
2395
|
+
validate: (input) => input.length > 0 || "Select at least one carrier"
|
|
2396
|
+
}
|
|
2397
|
+
]);
|
|
2398
|
+
carrierIds = selectedCarriers;
|
|
2399
|
+
} else if (options.all) {
|
|
2400
|
+
carrierIds = availableCarriers.map((c) => c.id);
|
|
2401
|
+
info(`Requesting quotes from all ${carrierIds.length} carriers`);
|
|
2402
|
+
}
|
|
2403
|
+
if (carrierIds.length === 0) {
|
|
2404
|
+
error("No carriers specified");
|
|
2405
|
+
info("Use --carriers, --all, or --interactive to select carriers");
|
|
2406
|
+
process.exit(1);
|
|
2407
|
+
}
|
|
2408
|
+
const spinner = (0, import_ora7.default)(`Submitting quote request to ${carrierIds.length} carriers...`).start();
|
|
2409
|
+
try {
|
|
2410
|
+
const result = await client.quotes.submit({
|
|
2411
|
+
carriers: carrierIds,
|
|
2412
|
+
priority: options.priority
|
|
2413
|
+
});
|
|
2414
|
+
if (result.success) {
|
|
2415
|
+
spinner.succeed(result.message);
|
|
2416
|
+
} else {
|
|
2417
|
+
spinner.warn(result.message);
|
|
2418
|
+
}
|
|
2419
|
+
if (options.json) {
|
|
2420
|
+
json(result);
|
|
2421
|
+
} else {
|
|
2422
|
+
console.log();
|
|
2423
|
+
if (result.carriers && result.carriers.length > 0) {
|
|
2424
|
+
info("Queued for:");
|
|
2425
|
+
for (const carrier of result.carriers) {
|
|
2426
|
+
console.log(` - ${carrier.name}`);
|
|
2427
|
+
}
|
|
2428
|
+
}
|
|
2429
|
+
if (result.invalidCarriers && result.invalidCarriers.length > 0) {
|
|
2430
|
+
console.log();
|
|
2431
|
+
warn(`Invalid carriers: ${result.invalidCarriers.join(", ")}`);
|
|
2432
|
+
}
|
|
2433
|
+
if (result.errors && result.errors.length > 0) {
|
|
2434
|
+
console.log();
|
|
2435
|
+
for (const err of result.errors) {
|
|
2436
|
+
error(err);
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
2439
|
+
console.log();
|
|
2440
|
+
info(`Queue IDs: ${result.queueIds.join(", ")}`);
|
|
2441
|
+
info('Use "delegance quote list" to check status');
|
|
2442
|
+
}
|
|
2443
|
+
} catch (err) {
|
|
2444
|
+
spinner.fail("Failed to submit quote request");
|
|
2445
|
+
if (err instanceof import_sdk5.NotImplementedError) {
|
|
2446
|
+
error(QUOTE_SUBMISSION_UNAVAILABLE);
|
|
2447
|
+
info("For an instant quote from the terminal: delegance quote start --coverage gl");
|
|
2448
|
+
info(`Or start a quote at ${PORTAL_URL}`);
|
|
2449
|
+
process.exit(1);
|
|
2450
|
+
}
|
|
2451
|
+
failWith("API error", err);
|
|
2452
|
+
}
|
|
2453
|
+
});
|
|
2454
|
+
quote.command("list").alias("ls").description("List quote requests").option("-s, --status <status>", "Filter by status (pending, processing, completed, failed)").option("-l, --limit <n>", "Maximum results", "20").option("--json", "Output as JSON").action(async (options) => {
|
|
2455
|
+
const client = getClient();
|
|
2456
|
+
const spinner = (0, import_ora7.default)("Fetching quotes...").start();
|
|
2457
|
+
try {
|
|
2458
|
+
const { quotes, count } = await client.quotes.list({
|
|
2459
|
+
status: options.status,
|
|
2460
|
+
limit: parseInt(options.limit)
|
|
2461
|
+
});
|
|
2462
|
+
spinner.succeed(`Found ${count} quotes`);
|
|
2463
|
+
if (quotes.length === 0) {
|
|
2464
|
+
info("No quotes found");
|
|
2465
|
+
return;
|
|
2466
|
+
}
|
|
2467
|
+
if (options.json) {
|
|
2468
|
+
json(quotes);
|
|
2469
|
+
} else {
|
|
2470
|
+
console.log();
|
|
2471
|
+
table(
|
|
2472
|
+
["ID", "Carrier", "Status", "Priority", "Created"],
|
|
2473
|
+
quotes.map((q) => [
|
|
2474
|
+
`${q.id.substring(0, 8)}...`,
|
|
2475
|
+
q.carrierName,
|
|
2476
|
+
formatQuoteStatus(q.status),
|
|
2477
|
+
q.priority,
|
|
2478
|
+
formatDate(q.createdAt)
|
|
2479
|
+
])
|
|
2480
|
+
);
|
|
2481
|
+
}
|
|
2482
|
+
} catch (err) {
|
|
2483
|
+
spinner.fail("Failed to list quotes");
|
|
2484
|
+
failWith("API error", err);
|
|
2485
|
+
}
|
|
2486
|
+
});
|
|
2487
|
+
quote.command("wait <quoteIds...>").description("Wait for quote(s) to complete").option("--timeout <ms>", "Timeout in milliseconds", "300000").option("--json", "Output as JSON").action(async (quoteIds, options) => {
|
|
2488
|
+
const client = getClient();
|
|
2489
|
+
const timeoutMs = parseInt(options.timeout);
|
|
2490
|
+
info(`Waiting for ${quoteIds.length} quote(s) to complete...`);
|
|
2491
|
+
console.log();
|
|
2492
|
+
const results = [];
|
|
2493
|
+
for (const quoteId of quoteIds) {
|
|
2494
|
+
const spinner = (0, import_ora7.default)(`Waiting for ${quoteId.substring(0, 8)}...`).start();
|
|
2495
|
+
try {
|
|
2496
|
+
const quote2 = await client.quotes.waitForCompletion(quoteId, { timeoutMs });
|
|
2497
|
+
if (quote2.status === "completed") {
|
|
2498
|
+
spinner.succeed(`${quote2.carrierName}: Completed`);
|
|
2499
|
+
if (quote2.result?.premium) {
|
|
2500
|
+
info(` Premium: $${quote2.result.premium.toLocaleString()}`);
|
|
2501
|
+
}
|
|
2502
|
+
} else if (quote2.status === "failed") {
|
|
2503
|
+
spinner.fail(`${quote2.carrierName}: Failed - ${quote2.error || "Unknown error"}`);
|
|
2504
|
+
} else {
|
|
2505
|
+
spinner.warn(`${quote2.carrierName}: ${quote2.status}`);
|
|
2506
|
+
}
|
|
2507
|
+
results.push({ id: quoteId, status: quote2.status, error: quote2.error });
|
|
2508
|
+
} catch (err) {
|
|
2509
|
+
spinner.fail(`${quoteId.substring(0, 8)}: ${err.message}`);
|
|
2510
|
+
results.push({ id: quoteId, status: "error", error: err.message });
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2513
|
+
if (options.json) {
|
|
2514
|
+
json(results);
|
|
2515
|
+
}
|
|
2516
|
+
});
|
|
2517
|
+
quote.command("get <quoteId>").description("Show one quote request").option("--json", "Output as JSON").action(async (quoteId, options) => {
|
|
2518
|
+
const client = getClient();
|
|
2519
|
+
const spinner = (0, import_ora7.default)("Fetching quote...").start();
|
|
2520
|
+
try {
|
|
2521
|
+
const { quote: q, canCancel } = await client.quotes.get(quoteId);
|
|
2522
|
+
spinner.succeed("Quote found");
|
|
2523
|
+
if (options.json) {
|
|
2524
|
+
json({ quote: q, canCancel });
|
|
2525
|
+
return;
|
|
2526
|
+
}
|
|
2527
|
+
console.log();
|
|
2528
|
+
header(`${q.carrierName} quote`);
|
|
2529
|
+
keyValue({
|
|
2530
|
+
"ID": q.id,
|
|
2531
|
+
"Status": formatQuoteStatus(q.status),
|
|
2532
|
+
"Priority": q.priority,
|
|
2533
|
+
"Created": formatDate(q.createdAt),
|
|
2534
|
+
"Completed": q.completedAt ? formatDate(q.completedAt) : null,
|
|
2535
|
+
"Premium": q.result?.premium ?? null,
|
|
2536
|
+
"Bindable": q.result?.bindable ?? null,
|
|
2537
|
+
"Error": q.error ?? null,
|
|
2538
|
+
"Can cancel": canCancel
|
|
2539
|
+
});
|
|
2540
|
+
} catch (err) {
|
|
2541
|
+
spinner.fail("Failed to fetch quote");
|
|
2542
|
+
failWith("API error", err);
|
|
2543
|
+
}
|
|
2544
|
+
});
|
|
2545
|
+
quote.command("cancel <quoteId>").description("Cancel a pending quote request").option("--json", "Output as JSON").action(async (quoteId, options) => {
|
|
2546
|
+
const client = getClient();
|
|
2547
|
+
const spinner = (0, import_ora7.default)("Cancelling quote...").start();
|
|
2548
|
+
try {
|
|
2549
|
+
const result = await client.quotes.cancel(quoteId);
|
|
2550
|
+
spinner.succeed(result.message || "Quote cancelled");
|
|
2551
|
+
if (options.json) json(result);
|
|
2552
|
+
} catch (err) {
|
|
2553
|
+
spinner.fail("Failed to cancel quote");
|
|
2554
|
+
failWith("API error", err);
|
|
2555
|
+
}
|
|
2556
|
+
});
|
|
2557
|
+
addInstantQuoteCommands(quote);
|
|
2558
|
+
return quote;
|
|
2559
|
+
}
|
|
2560
|
+
function formatQuoteStatus(status) {
|
|
2561
|
+
switch (status) {
|
|
2562
|
+
case "completed":
|
|
2563
|
+
return "\u2713 completed";
|
|
2564
|
+
case "pending":
|
|
2565
|
+
return "\u25CB pending";
|
|
2566
|
+
case "processing":
|
|
2567
|
+
return "\u25D0 processing";
|
|
2568
|
+
case "failed":
|
|
2569
|
+
return "\u2717 failed";
|
|
2570
|
+
case "cancelled":
|
|
2571
|
+
return "\u2298 cancelled";
|
|
2572
|
+
default:
|
|
2573
|
+
return status;
|
|
2574
|
+
}
|
|
2575
|
+
}
|
|
2576
|
+
|
|
2577
|
+
// src/commands/policy.ts
|
|
2578
|
+
var import_commander7 = require("commander");
|
|
2579
|
+
var import_inquirer7 = __toESM(require("inquirer"));
|
|
2580
|
+
var import_ora8 = __toESM(require("ora"));
|
|
2581
|
+
function createPolicyCommand() {
|
|
2582
|
+
const policy = new import_commander7.Command("policy").alias("policies").description("Manage insurance policies");
|
|
2583
|
+
policy.command("list").alias("ls").description("List all policies").option("-s, --status <status>", "Filter by status (active, expired, all)", "active").option("-t, --type <type>", "Filter by policy type").option("-l, --limit <n>", "Maximum results", "50").option("--json", "Output as JSON").action(async (options) => {
|
|
2584
|
+
const client = getClient();
|
|
2585
|
+
const spinner = (0, import_ora8.default)("Fetching policies...").start();
|
|
2586
|
+
try {
|
|
2587
|
+
const { policies, count } = await client.policies.list({
|
|
2588
|
+
status: options.status === "all" ? void 0 : options.status,
|
|
2589
|
+
policyType: options.type,
|
|
2590
|
+
limit: parseInt(options.limit)
|
|
2591
|
+
});
|
|
2592
|
+
spinner.succeed(`Found ${count} policies`);
|
|
2593
|
+
if (policies.length === 0) {
|
|
2594
|
+
info("No policies found");
|
|
2595
|
+
return;
|
|
2596
|
+
}
|
|
2597
|
+
if (options.json) {
|
|
2598
|
+
json(policies);
|
|
2599
|
+
} else {
|
|
2600
|
+
console.log();
|
|
2601
|
+
table(
|
|
2602
|
+
["Carrier", "Type", "Policy #", "Expiry", "Status", "Premium"],
|
|
2603
|
+
policies.map((p) => [
|
|
2604
|
+
p.carrier || "Unknown",
|
|
2605
|
+
p.policyType || p.name || "-",
|
|
2606
|
+
p.policyNumber || "-",
|
|
2607
|
+
p.expiryDate ? formatDate(p.expiryDate) : "-",
|
|
2608
|
+
formatPolicyStatus(p.isActive, p.daysUntilExpiry),
|
|
2609
|
+
p.premium ? formatCurrency(p.premium) : "-"
|
|
2610
|
+
])
|
|
2611
|
+
);
|
|
2612
|
+
}
|
|
2613
|
+
} catch (err) {
|
|
2614
|
+
spinner.fail("Failed to fetch policies");
|
|
2615
|
+
failWith("API error", err);
|
|
2616
|
+
}
|
|
2617
|
+
});
|
|
2618
|
+
policy.command("expiring [days]").description("Show policies expiring soon").option("--json", "Output as JSON").action(async (days = "30", options) => {
|
|
2619
|
+
const client = getClient();
|
|
2620
|
+
const daysNum = parseInt(days, 10);
|
|
2621
|
+
const spinner = (0, import_ora8.default)(`Fetching policies expiring in ${daysNum} days...`).start();
|
|
2622
|
+
try {
|
|
2623
|
+
const { policies, count } = await client.policies.getExpiring(daysNum);
|
|
2624
|
+
spinner.succeed(`Found ${count} policies expiring in ${daysNum} days`);
|
|
2625
|
+
if (policies.length === 0) {
|
|
2626
|
+
success("No policies expiring soon!");
|
|
2627
|
+
return;
|
|
2628
|
+
}
|
|
2629
|
+
if (options.json) {
|
|
2630
|
+
json(policies);
|
|
2631
|
+
} else {
|
|
2632
|
+
console.log();
|
|
2633
|
+
const urgent = policies.filter((p) => (p.daysUntilExpiry || 0) <= 7);
|
|
2634
|
+
const soon = policies.filter((p) => (p.daysUntilExpiry || 0) > 7 && (p.daysUntilExpiry || 0) <= 30);
|
|
2635
|
+
const later = policies.filter((p) => (p.daysUntilExpiry || 0) > 30);
|
|
2636
|
+
if (urgent.length > 0) {
|
|
2637
|
+
warn(`\u26A0\uFE0F Expiring within 7 days (${urgent.length}):`);
|
|
2638
|
+
for (const p of urgent) {
|
|
2639
|
+
console.log(` - ${p.carrier} ${p.policyType || p.name} - expires ${formatDate(p.expiryDate)}`);
|
|
2640
|
+
}
|
|
2641
|
+
console.log();
|
|
2642
|
+
}
|
|
2643
|
+
if (soon.length > 0) {
|
|
2644
|
+
info(`\u{1F4C5} Expiring within 30 days (${soon.length}):`);
|
|
2645
|
+
for (const p of soon) {
|
|
2646
|
+
console.log(` - ${p.carrier} ${p.policyType || p.name} - expires ${formatDate(p.expiryDate)}`);
|
|
2647
|
+
}
|
|
2648
|
+
console.log();
|
|
2649
|
+
}
|
|
2650
|
+
if (later.length > 0) {
|
|
2651
|
+
console.log(`\u{1F4C6} Expiring later (${later.length}):`);
|
|
2652
|
+
for (const p of later) {
|
|
2653
|
+
console.log(` - ${p.carrier} ${p.policyType || p.name} - ${p.daysUntilExpiry} days`);
|
|
2654
|
+
}
|
|
2655
|
+
}
|
|
2656
|
+
}
|
|
2657
|
+
} catch (err) {
|
|
2658
|
+
spinner.fail("Failed to fetch expiring policies");
|
|
2659
|
+
failWith("API error", err);
|
|
2660
|
+
}
|
|
2661
|
+
});
|
|
2662
|
+
policy.command("get <id>").description("Get policy details").option("--json", "Output as JSON").action(async (id, options) => {
|
|
2663
|
+
const client = getClient();
|
|
2664
|
+
const spinner = (0, import_ora8.default)("Fetching policy...").start();
|
|
2665
|
+
try {
|
|
2666
|
+
const found = await client.policies.get(id);
|
|
2667
|
+
if (!found) {
|
|
2668
|
+
spinner.fail("Policy not found");
|
|
2669
|
+
process.exit(1);
|
|
2670
|
+
}
|
|
2671
|
+
spinner.succeed("Policy found");
|
|
2672
|
+
if (options.json) {
|
|
2673
|
+
json(found);
|
|
2674
|
+
} else {
|
|
2675
|
+
const { policy: p, coverages } = found;
|
|
2676
|
+
console.log();
|
|
2677
|
+
header(`${p.carrier} - ${p.policyType || p.name}`);
|
|
2678
|
+
console.log();
|
|
2679
|
+
console.log(` Policy #: ${p.policyNumber || "N/A"}`);
|
|
2680
|
+
console.log(` Carrier: ${p.carrier}`);
|
|
2681
|
+
console.log(` Type: ${p.policyType || "N/A"}`);
|
|
2682
|
+
console.log(` Effective: ${p.effectiveDate ? formatDate(p.effectiveDate) : "N/A"}`);
|
|
2683
|
+
console.log(` Expiry: ${p.expiryDate ? formatDate(p.expiryDate) : "N/A"}`);
|
|
2684
|
+
console.log(` Status: ${formatPolicyStatus(p.isActive, p.daysUntilExpiry)}`);
|
|
2685
|
+
console.log();
|
|
2686
|
+
console.log(` Premium: ${p.premium ? formatCurrency(p.premium) : "N/A"}`);
|
|
2687
|
+
console.log(` Deductible: ${p.deductible ? formatCurrency(p.deductible) : "N/A"}`);
|
|
2688
|
+
console.log(` Occurrence: ${p.occurrenceLimit ? formatCurrency(p.occurrenceLimit) : "N/A"}`);
|
|
2689
|
+
console.log(` Aggregate: ${p.aggregateLimit ? formatCurrency(p.aggregateLimit) : "N/A"}`);
|
|
2690
|
+
if (p.naicCode) {
|
|
2691
|
+
console.log(` NAIC: ${p.naicCode}`);
|
|
2692
|
+
}
|
|
2693
|
+
if (coverages.length > 0) {
|
|
2694
|
+
console.log();
|
|
2695
|
+
table(
|
|
2696
|
+
["Coverage", "Limit", "Deductible"],
|
|
2697
|
+
coverages.map((c) => [
|
|
2698
|
+
c.coverageType,
|
|
2699
|
+
c.limit ? formatCurrency(c.limit) : "-",
|
|
2700
|
+
c.deductible ? formatCurrency(c.deductible) : "-"
|
|
2701
|
+
])
|
|
2702
|
+
);
|
|
2703
|
+
}
|
|
2704
|
+
}
|
|
2705
|
+
} catch (err) {
|
|
2706
|
+
spinner.fail("Failed to fetch policy");
|
|
2707
|
+
failWith("API error", err);
|
|
2708
|
+
}
|
|
2709
|
+
});
|
|
2710
|
+
policy.command("docs <id>").description("The documents a policy was read from: declarations, forms, endorsements").option("--json", "Output as JSON").action(async (id, options) => {
|
|
2711
|
+
const client = getClient();
|
|
2712
|
+
try {
|
|
2713
|
+
const found = await client.policies.get(id);
|
|
2714
|
+
if (!found) {
|
|
2715
|
+
error("Policy not found");
|
|
2716
|
+
process.exit(1);
|
|
2717
|
+
}
|
|
2718
|
+
const ids = (found.policy.sourceDocuments ?? []).slice(0, 20);
|
|
2719
|
+
const docs = await Promise.all(ids.map(async (docId) => {
|
|
2720
|
+
try {
|
|
2721
|
+
const d = await client.documents.get(docId);
|
|
2722
|
+
return { id: d.id, fileName: d.fileName, documentType: d.documentType ?? null, status: d.status, createdAt: d.createdAt };
|
|
2723
|
+
} catch {
|
|
2724
|
+
return { id: docId, fileName: null, documentType: null, status: "unavailable", createdAt: null };
|
|
2725
|
+
}
|
|
2726
|
+
}));
|
|
2727
|
+
if (options.json) {
|
|
2728
|
+
json({ policyId: id, documents: docs });
|
|
2729
|
+
return;
|
|
2730
|
+
}
|
|
2731
|
+
if (docs.length === 0) {
|
|
2732
|
+
info("No source documents are linked to this policy yet. Upload one with: delegance document upload <file>");
|
|
2733
|
+
return;
|
|
2734
|
+
}
|
|
2735
|
+
table(
|
|
2736
|
+
["Document", "File", "Type", "Status", "Added"],
|
|
2737
|
+
docs.map((d) => [d.id, d.fileName ?? "-", d.documentType ?? "-", d.status, d.createdAt ? formatDate(d.createdAt) : "-"])
|
|
2738
|
+
);
|
|
2739
|
+
info("Download one with: delegance document status <documentId>");
|
|
2740
|
+
} catch (err) {
|
|
2741
|
+
failWith("API error", err);
|
|
2742
|
+
}
|
|
2743
|
+
});
|
|
2744
|
+
policy.command("cancel <policyId>").description("Ask to cancel a policy (owner or admin only); sign now with an emailed code, and your broker checks for a coverage gap first").requiredOption("--effective <date>", "Cancellation date, YYYY-MM-DD (coverage ends at 12:01 AM that day)").requiredOption("--reason <reason>", "replaced | business_closed | sold | not_needed | other").option("--replacement <policyId>", "The policy that replaces this one (required with --reason replaced)").option("--signer-name <name>", "Sign now: your full name").option("--signer-title <title>", "Sign now: your title").option("--attest", "Sign now: you are authorized to bind the named insured").option("--json", "Output as JSON").action(async (policyId, options) => {
|
|
2745
|
+
const sig = signatureFrom(options);
|
|
2746
|
+
if (sig === "partial") {
|
|
2747
|
+
failWith("Missing signer details", new Error("To sign now, pass --signer-name, --signer-title and --attest together."));
|
|
2748
|
+
return;
|
|
2749
|
+
}
|
|
2750
|
+
const client = getClient();
|
|
2751
|
+
const spinner = (0, import_ora8.default)("Requesting cancellation...").start();
|
|
2752
|
+
try {
|
|
2753
|
+
const out = await client.policies.requestCancellation(policyId, {
|
|
2754
|
+
requestedEffectiveDate: options.effective,
|
|
2755
|
+
reason: options.reason,
|
|
2756
|
+
replacementPolicyId: options.replacement
|
|
2757
|
+
});
|
|
2758
|
+
spinner.succeed("Cancellation requested");
|
|
2759
|
+
if (options.json) {
|
|
2760
|
+
json(out);
|
|
2761
|
+
return;
|
|
2762
|
+
}
|
|
2763
|
+
console.log();
|
|
2764
|
+
console.log(` Request: ${out.request.id}`);
|
|
2765
|
+
console.log(` Status: ${out.request.status.replace(/_/g, " ")}`);
|
|
2766
|
+
console.log(` Continuity: ${out.request.continuity.replace(/_/g, " ")}`);
|
|
2767
|
+
console.log();
|
|
2768
|
+
if (sig) {
|
|
2769
|
+
await signWithStepUp(client, out.request.id, sig, void 0, void 0);
|
|
2770
|
+
} else {
|
|
2771
|
+
info(`Sign here: ${out.signatureUrl}`);
|
|
2772
|
+
info(`Or sign from here: delegance policy sign ${out.request.id} --signer-name "..." --signer-title "..." --attest`);
|
|
2773
|
+
}
|
|
2774
|
+
info(`Check progress with "delegance policy cancellation ${out.request.id}"`);
|
|
2775
|
+
} catch (err) {
|
|
2776
|
+
spinner.fail("Could not request the cancellation");
|
|
2777
|
+
failWith("API error", err);
|
|
2778
|
+
}
|
|
2779
|
+
});
|
|
2780
|
+
policy.command("sign <requestId>").description("Sign a cancellation request as an owner or admin; we email you a 6-digit code to confirm").requiredOption("--signer-name <name>", "Your full name").requiredOption("--signer-title <title>", "Your title").requiredOption("--attest", "You are authorized to bind the named insured").option("--step-up <id>", "Finish an earlier attempt: the stepUpId the code email belongs to").option("--code <code>", "The 6-digit code (with --step-up), for scripts").action(async (requestId, options) => {
|
|
2781
|
+
const client = getClient();
|
|
2782
|
+
const sig = signatureFrom(options);
|
|
2783
|
+
if (!sig || sig === "partial") {
|
|
2784
|
+
failWith("Missing signer details", new Error("Pass --signer-name, --signer-title and --attest."));
|
|
2785
|
+
return;
|
|
2786
|
+
}
|
|
2787
|
+
try {
|
|
2788
|
+
await signWithStepUp(client, requestId, sig, options.stepUp, options.code);
|
|
2789
|
+
} catch (err) {
|
|
2790
|
+
failWith("Could not sign", err);
|
|
2791
|
+
}
|
|
2792
|
+
});
|
|
2793
|
+
policy.command("cancellation <requestId>").description("Show a cancellation request's status").option("--json", "Output as JSON").action(async (requestId, options) => {
|
|
2794
|
+
const client = getClient();
|
|
2795
|
+
try {
|
|
2796
|
+
const r = await client.policies.getCancellation(requestId);
|
|
2797
|
+
if (options.json) {
|
|
2798
|
+
json(r);
|
|
2799
|
+
return;
|
|
2800
|
+
}
|
|
2801
|
+
console.log(` Status: ${r.status.replace(/_/g, " ")}`);
|
|
2802
|
+
console.log(` Requested: ${r.requestedEffectiveDate}`);
|
|
2803
|
+
console.log(` Continuity: ${r.continuity.replace(/_/g, " ")}`);
|
|
2804
|
+
if (r.confirmationNumber) console.log(` Confirmed: ${r.confirmedEffectiveDate} (confirmation ${r.confirmationNumber})`);
|
|
2805
|
+
} catch (err) {
|
|
2806
|
+
failWith("API error", err);
|
|
2807
|
+
}
|
|
2808
|
+
});
|
|
2809
|
+
return policy;
|
|
2810
|
+
}
|
|
2811
|
+
function signatureFrom(o) {
|
|
2812
|
+
const given = [o.signerName, o.signerTitle, o.attest].filter((v) => v !== void 0 && v !== false).length;
|
|
2813
|
+
if (given === 0) return null;
|
|
2814
|
+
if (given < 3) return "partial";
|
|
2815
|
+
return { signerName: String(o.signerName), signerTitle: String(o.signerTitle), attestAuthority: true };
|
|
2816
|
+
}
|
|
2817
|
+
async function signWithStepUp(client, requestId, sig, stepUpId, presetCode) {
|
|
2818
|
+
let id = stepUpId;
|
|
2819
|
+
if (!id || !presetCode) {
|
|
2820
|
+
const first = await client.policies.signCancellation(requestId, sig);
|
|
2821
|
+
if (!("stepUpRequired" in first)) {
|
|
2822
|
+
success("Signed. Your broker checks the request and sends it to the carrier.");
|
|
2823
|
+
return;
|
|
2824
|
+
}
|
|
2825
|
+
id = first.stepUpId;
|
|
2826
|
+
info(`We emailed a 6-digit code to ${first.sentTo}. It expires in 10 minutes.`);
|
|
2827
|
+
}
|
|
2828
|
+
let code = presetCode;
|
|
2829
|
+
if (!code) {
|
|
2830
|
+
if (!process.stdin.isTTY) {
|
|
2831
|
+
warn(`No terminal to type the code in. Finish with: delegance policy sign ${requestId} --signer-name "${sig.signerName}" --signer-title "${sig.signerTitle}" --attest --step-up ${id} --code <code>`);
|
|
2832
|
+
return;
|
|
2833
|
+
}
|
|
2834
|
+
const answer = await import_inquirer7.default.prompt([{
|
|
2835
|
+
type: "input",
|
|
2836
|
+
name: "code",
|
|
2837
|
+
message: "Code from your email:",
|
|
2838
|
+
validate: (v) => /^\d{6}$/.test(v.trim()) || "Enter the 6 digits from the email"
|
|
2839
|
+
}]);
|
|
2840
|
+
code = String(answer.code).trim();
|
|
2841
|
+
}
|
|
2842
|
+
await client.policies.signCancellation(requestId, { ...sig, stepUpId: id, code });
|
|
2843
|
+
success("Signed. Your broker checks the request and sends it to the carrier.");
|
|
2844
|
+
}
|
|
2845
|
+
function formatPolicyStatus(isActive, daysUntilExpiry) {
|
|
2846
|
+
if (!isActive) {
|
|
2847
|
+
return "\u2717 Expired";
|
|
2848
|
+
}
|
|
2849
|
+
if (daysUntilExpiry !== null && daysUntilExpiry !== void 0) {
|
|
2850
|
+
if (daysUntilExpiry <= 7) {
|
|
2851
|
+
return `\u26A0\uFE0F ${daysUntilExpiry}d`;
|
|
2852
|
+
}
|
|
2853
|
+
if (daysUntilExpiry <= 30) {
|
|
2854
|
+
return `\u{1F4C5} ${daysUntilExpiry}d`;
|
|
2855
|
+
}
|
|
2856
|
+
}
|
|
2857
|
+
return "\u2713 Active";
|
|
2858
|
+
}
|
|
2859
|
+
|
|
2860
|
+
// src/commands/coi.ts
|
|
2861
|
+
var import_commander8 = require("commander");
|
|
2862
|
+
var import_ora9 = __toESM(require("ora"));
|
|
2863
|
+
var import_inquirer8 = __toESM(require("inquirer"));
|
|
2864
|
+
function createCOICommand() {
|
|
2865
|
+
const coi = new import_commander8.Command("coi").description("Generate and manage Certificates of Insurance");
|
|
2866
|
+
coi.command("generate").alias("create").description("Generate a Certificate of Insurance").option("-p, --policies <ids...>", "Policy IDs to include").option("-n, --holder-name <name>", "Certificate holder name").option("-a, --holder-address <address>", "Certificate holder address").option("--city <city>", "Certificate holder city").option("--state <state>", "Certificate holder state (2-letter)").option("--zip <zip>", "Certificate holder ZIP").option("-t, --type <type>", "Certificate type (additional_insured, certificate_holder)", "certificate_holder").option("-i, --interactive", "Interactive mode").option("--json", "Output as JSON").action(async (options) => {
|
|
2867
|
+
const client = getClient();
|
|
2868
|
+
let policyIds = options.policies || [];
|
|
2869
|
+
let { holderName } = options;
|
|
2870
|
+
let { holderAddress } = options;
|
|
2871
|
+
let { city } = options;
|
|
2872
|
+
let { state } = options;
|
|
2873
|
+
let { zip } = options;
|
|
2874
|
+
let certificateType = options.type;
|
|
2875
|
+
if (options.interactive || !holderName || policyIds.length === 0) {
|
|
2876
|
+
const policiesSpinner = (0, import_ora9.default)("Fetching active policies...").start();
|
|
2877
|
+
const { policies } = await client.policies.list({ status: "active" });
|
|
2878
|
+
policiesSpinner.stop();
|
|
2879
|
+
if (policies.length === 0) {
|
|
2880
|
+
error("No active policies found");
|
|
2881
|
+
info('Upload policy documents first with "delegance doc upload"');
|
|
2882
|
+
process.exit(1);
|
|
2883
|
+
}
|
|
2884
|
+
const answers = await import_inquirer8.default.prompt([
|
|
2885
|
+
{
|
|
2886
|
+
type: "checkbox",
|
|
2887
|
+
name: "selectedPolicies",
|
|
2888
|
+
message: "Select policies to include:",
|
|
2889
|
+
choices: policies.map((p) => ({
|
|
2890
|
+
name: `${p.carrier} - ${p.policyType || p.name} (${p.policyNumber || "No #"})`,
|
|
2891
|
+
value: p.id,
|
|
2892
|
+
checked: policyIds.includes(p.id)
|
|
2893
|
+
})),
|
|
2894
|
+
validate: (input) => input.length > 0 || "Select at least one policy"
|
|
2895
|
+
},
|
|
2896
|
+
{
|
|
2897
|
+
type: "input",
|
|
2898
|
+
name: "holderName",
|
|
2899
|
+
message: "Certificate holder name:",
|
|
2900
|
+
default: holderName,
|
|
2901
|
+
validate: (input) => input.length >= 2 || "Name must be at least 2 characters"
|
|
2902
|
+
},
|
|
2903
|
+
{
|
|
2904
|
+
type: "input",
|
|
2905
|
+
name: "holderAddress",
|
|
2906
|
+
message: "Certificate holder address:",
|
|
2907
|
+
default: holderAddress
|
|
2908
|
+
},
|
|
2909
|
+
{
|
|
2910
|
+
type: "input",
|
|
2911
|
+
name: "city",
|
|
2912
|
+
message: "City:",
|
|
2913
|
+
default: city
|
|
2914
|
+
},
|
|
2915
|
+
{
|
|
2916
|
+
type: "input",
|
|
2917
|
+
name: "state",
|
|
2918
|
+
message: "State (2-letter):",
|
|
2919
|
+
default: state,
|
|
2920
|
+
validate: (input) => !input || input.length === 2 || "State must be 2 letters (e.g., CA)"
|
|
2921
|
+
},
|
|
2922
|
+
{
|
|
2923
|
+
type: "input",
|
|
2924
|
+
name: "zip",
|
|
2925
|
+
message: "ZIP code:",
|
|
2926
|
+
default: zip
|
|
2927
|
+
},
|
|
2928
|
+
{
|
|
2929
|
+
type: "list",
|
|
2930
|
+
name: "certificateType",
|
|
2931
|
+
message: "Certificate type:",
|
|
2932
|
+
choices: [
|
|
2933
|
+
{ name: "Certificate Holder Only", value: "certificate_holder" },
|
|
2934
|
+
{ name: "Additional Insured", value: "additional_insured" }
|
|
2935
|
+
],
|
|
2936
|
+
default: certificateType
|
|
2937
|
+
}
|
|
2938
|
+
]);
|
|
2939
|
+
policyIds = answers.selectedPolicies;
|
|
2940
|
+
holderName = answers.holderName;
|
|
2941
|
+
holderAddress = answers.holderAddress;
|
|
2942
|
+
city = answers.city;
|
|
2943
|
+
state = answers.state;
|
|
2944
|
+
zip = answers.zip;
|
|
2945
|
+
certificateType = answers.certificateType;
|
|
2946
|
+
}
|
|
2947
|
+
if (!holderName) {
|
|
2948
|
+
error("Certificate holder name is required");
|
|
2949
|
+
process.exit(1);
|
|
2950
|
+
}
|
|
2951
|
+
const spinner = (0, import_ora9.default)("Generating COI...").start();
|
|
2952
|
+
try {
|
|
2953
|
+
const result = await client.coi.generate({
|
|
2954
|
+
policyIds,
|
|
2955
|
+
certificateHolder: {
|
|
2956
|
+
name: holderName,
|
|
2957
|
+
address: holderAddress,
|
|
2958
|
+
city,
|
|
2959
|
+
state,
|
|
2960
|
+
zip
|
|
2961
|
+
},
|
|
2962
|
+
certificateType
|
|
2963
|
+
});
|
|
2964
|
+
spinner.succeed("COI generated successfully!");
|
|
2965
|
+
if (options.json) {
|
|
2966
|
+
json(result);
|
|
2967
|
+
} else {
|
|
2968
|
+
console.log();
|
|
2969
|
+
success(`Certificate of Insurance created for ${holderName}`);
|
|
2970
|
+
console.log();
|
|
2971
|
+
info(`COI ID: ${result.coiId}`);
|
|
2972
|
+
info(`Download URL: ${result.downloadUrl}`);
|
|
2973
|
+
info(`URL expires: ${formatDate(result.expiresAt)}`);
|
|
2974
|
+
console.log();
|
|
2975
|
+
console.log("Policies included:");
|
|
2976
|
+
for (const p of result.policiesIncluded) {
|
|
2977
|
+
console.log(` - ${p.carrier} (${p.policyType})`);
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
} catch (err) {
|
|
2981
|
+
spinner.fail("Failed to generate COI");
|
|
2982
|
+
failWith("API error", err);
|
|
2983
|
+
}
|
|
2984
|
+
});
|
|
2985
|
+
coi.command("list").alias("ls").description("List generated COIs").option("-l, --limit <n>", "Maximum results", "20").option("--json", "Output as JSON").action(async (options) => {
|
|
2986
|
+
const client = getClient();
|
|
2987
|
+
const spinner = (0, import_ora9.default)("Fetching COIs...").start();
|
|
2988
|
+
try {
|
|
2989
|
+
const { cois, count } = await client.coi.list(parseInt(options.limit));
|
|
2990
|
+
spinner.succeed(`Found ${count} COIs`);
|
|
2991
|
+
if (cois.length === 0) {
|
|
2992
|
+
info("No COIs generated yet");
|
|
2993
|
+
info('Generate one with "delegance coi generate --interactive"');
|
|
2994
|
+
return;
|
|
2995
|
+
}
|
|
2996
|
+
if (options.json) {
|
|
2997
|
+
json(cois);
|
|
2998
|
+
} else {
|
|
2999
|
+
console.log();
|
|
3000
|
+
table(
|
|
3001
|
+
["ID", "Certificate Holder", "Type", "Policies", "Created"],
|
|
3002
|
+
cois.map((c) => [
|
|
3003
|
+
`${c.id.substring(0, 8)}...`,
|
|
3004
|
+
c.certificateHolder,
|
|
3005
|
+
c.certificateType,
|
|
3006
|
+
String(c.policyIds?.length || 0),
|
|
3007
|
+
formatDate(c.createdAt)
|
|
3008
|
+
])
|
|
3009
|
+
);
|
|
3010
|
+
}
|
|
3011
|
+
} catch (err) {
|
|
3012
|
+
spinner.fail("Failed to fetch COIs");
|
|
3013
|
+
failWith("API error", err);
|
|
3014
|
+
}
|
|
3015
|
+
});
|
|
3016
|
+
return coi;
|
|
3017
|
+
}
|
|
3018
|
+
|
|
3019
|
+
// src/commands/sov.ts
|
|
3020
|
+
var import_commander9 = require("commander");
|
|
3021
|
+
var import_ora10 = __toESM(require("ora"));
|
|
3022
|
+
var import_fs = __toESM(require("fs"));
|
|
3023
|
+
var import_path = __toESM(require("path"));
|
|
3024
|
+
function createSOVCommand() {
|
|
3025
|
+
const sov = new import_commander9.Command("sov").description("Manage Schedules of Values (property schedules)");
|
|
3026
|
+
sov.command("upload <file>").description("Upload a Schedule of Values file").option("-l, --line <line>", "Line of business (property, inland_marine, etc)", "property").option("--effective <date>", "Effective date (YYYY-MM-DD)").option("--expiration <date>", "Expiration date (YYYY-MM-DD)").option("-w, --wait", "Wait for parsing to complete").option("--json", "Output as JSON").action(async (file, options) => {
|
|
3027
|
+
const client = getClient();
|
|
3028
|
+
const filePath = import_path.default.resolve(file);
|
|
3029
|
+
if (!import_fs.default.existsSync(filePath)) {
|
|
3030
|
+
error(`File not found: ${file}`);
|
|
3031
|
+
process.exit(1);
|
|
3032
|
+
}
|
|
3033
|
+
const fileName = import_path.default.basename(filePath);
|
|
3034
|
+
const ext = import_path.default.extname(fileName).toLowerCase();
|
|
3035
|
+
if (![".xlsx", ".xls", ".csv", ".pdf"].includes(ext)) {
|
|
3036
|
+
error("Unsupported file type. Supported: .xlsx, .xls, .csv, .pdf");
|
|
3037
|
+
process.exit(1);
|
|
3038
|
+
}
|
|
3039
|
+
const spinner = (0, import_ora10.default)(`Uploading ${fileName}...`).start();
|
|
3040
|
+
try {
|
|
3041
|
+
const fileBuffer = import_fs.default.readFileSync(filePath);
|
|
3042
|
+
const result = await client.sov.upload({
|
|
3043
|
+
file: fileBuffer,
|
|
3044
|
+
fileName,
|
|
3045
|
+
lineOfBusiness: options.line,
|
|
3046
|
+
effectiveDate: options.effective,
|
|
3047
|
+
expirationDate: options.expiration
|
|
3048
|
+
});
|
|
3049
|
+
spinner.succeed(`SOV uploaded: ${result.sovId}`);
|
|
3050
|
+
if (options.wait) {
|
|
3051
|
+
const parseSpinner = (0, import_ora10.default)("Waiting for parsing...").start();
|
|
3052
|
+
try {
|
|
3053
|
+
const parsed = await client.sov.waitForParsing(result.sovId);
|
|
3054
|
+
if (parsed.status === "error") {
|
|
3055
|
+
parseSpinner.fail("Parsing failed");
|
|
3056
|
+
} else {
|
|
3057
|
+
parseSpinner.succeed(`Parsing complete - ${parsed.totalLocations || 0} locations, TIV: ${formatCurrency(parsed.totalTIV || 0)}`);
|
|
3058
|
+
}
|
|
3059
|
+
} catch (waitErr) {
|
|
3060
|
+
parseSpinner.fail(waitErr.message);
|
|
3061
|
+
}
|
|
3062
|
+
}
|
|
3063
|
+
if (options.json) {
|
|
3064
|
+
json(result);
|
|
3065
|
+
} else {
|
|
3066
|
+
console.log();
|
|
3067
|
+
info(`SOV ID: ${result.sovId}`);
|
|
3068
|
+
info(`Status: ${result.status}`);
|
|
3069
|
+
if (!options.wait) {
|
|
3070
|
+
info('Use "delegance sov status <id>" to check parsing progress');
|
|
3071
|
+
}
|
|
3072
|
+
}
|
|
3073
|
+
} catch (err) {
|
|
3074
|
+
spinner.fail("Failed to upload SOV");
|
|
3075
|
+
failWith("API error", err);
|
|
3076
|
+
}
|
|
3077
|
+
});
|
|
3078
|
+
sov.command("list").alias("ls").description("List uploaded SOVs").option("-s, --status <status>", "Filter by status").option("-l, --limit <n>", "Maximum results", "20").option("--json", "Output as JSON").action(async (options) => {
|
|
3079
|
+
const client = getClient();
|
|
3080
|
+
const spinner = (0, import_ora10.default)("Fetching SOVs...").start();
|
|
3081
|
+
try {
|
|
3082
|
+
const { sovs, count } = await client.sov.list({
|
|
3083
|
+
status: options.status,
|
|
3084
|
+
limit: parseInt(options.limit)
|
|
3085
|
+
});
|
|
3086
|
+
spinner.succeed(`Found ${count} SOVs`);
|
|
3087
|
+
if (sovs.length === 0) {
|
|
3088
|
+
info("No SOVs found");
|
|
3089
|
+
info('Upload one with "delegance sov upload schedule.xlsx"');
|
|
3090
|
+
return;
|
|
3091
|
+
}
|
|
3092
|
+
if (options.json) {
|
|
3093
|
+
json(sovs);
|
|
3094
|
+
} else {
|
|
3095
|
+
console.log();
|
|
3096
|
+
table(
|
|
3097
|
+
["ID", "File Name", "Status", "Locations", "Total TIV", "Created"],
|
|
3098
|
+
sovs.map((s) => [
|
|
3099
|
+
`${s.id.substring(0, 8)}...`,
|
|
3100
|
+
s.fileName.substring(0, 25) + (s.fileName.length > 25 ? "..." : ""),
|
|
3101
|
+
formatSOVStatus(s.status),
|
|
3102
|
+
s.totalLocations?.toString() || "-",
|
|
3103
|
+
s.totalTIV ? formatCurrency(s.totalTIV) : "-",
|
|
3104
|
+
formatDate(s.createdAt)
|
|
3105
|
+
])
|
|
3106
|
+
);
|
|
3107
|
+
}
|
|
3108
|
+
} catch (err) {
|
|
3109
|
+
spinner.fail("Failed to fetch SOVs");
|
|
3110
|
+
failWith("API error", err);
|
|
3111
|
+
}
|
|
3112
|
+
});
|
|
3113
|
+
sov.command("status <id>").description("Check SOV status").option("-w, --wait", "Wait for parsing to complete").option("--json", "Output as JSON").action(async (id, options) => {
|
|
3114
|
+
const client = getClient();
|
|
3115
|
+
if (options.wait) {
|
|
3116
|
+
const spinner = (0, import_ora10.default)("Waiting for SOV processing...").start();
|
|
3117
|
+
try {
|
|
3118
|
+
const s = await client.sov.waitForParsing(id);
|
|
3119
|
+
if (s.status === "error") {
|
|
3120
|
+
spinner.fail("SOV processing failed");
|
|
3121
|
+
} else {
|
|
3122
|
+
spinner.succeed("SOV processing complete");
|
|
3123
|
+
}
|
|
3124
|
+
if (options.json) {
|
|
3125
|
+
json(s);
|
|
3126
|
+
} else {
|
|
3127
|
+
showSOVDetails(s);
|
|
3128
|
+
}
|
|
3129
|
+
} catch (err) {
|
|
3130
|
+
spinner.fail(err.message);
|
|
3131
|
+
process.exit(1);
|
|
3132
|
+
}
|
|
3133
|
+
} else {
|
|
3134
|
+
const spinner = (0, import_ora10.default)("Fetching SOV status...").start();
|
|
3135
|
+
try {
|
|
3136
|
+
const { sovs } = await client.sov.list({ limit: 100 });
|
|
3137
|
+
const s = sovs.find((x) => x.id === id);
|
|
3138
|
+
if (!s) {
|
|
3139
|
+
spinner.fail("SOV not found");
|
|
3140
|
+
process.exit(1);
|
|
3141
|
+
}
|
|
3142
|
+
spinner.succeed(`Status: ${formatSOVStatus(s.status)}`);
|
|
3143
|
+
if (options.json) {
|
|
3144
|
+
json(s);
|
|
3145
|
+
} else {
|
|
3146
|
+
showSOVDetails(s);
|
|
3147
|
+
}
|
|
3148
|
+
} catch (err) {
|
|
3149
|
+
spinner.fail("Failed to fetch SOV");
|
|
3150
|
+
failWith("API error", err);
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
});
|
|
3154
|
+
return sov;
|
|
3155
|
+
}
|
|
3156
|
+
function formatSOVStatus(status) {
|
|
3157
|
+
switch (status) {
|
|
3158
|
+
case "parsed":
|
|
3159
|
+
case "validated":
|
|
3160
|
+
return `\u2713 ${status}`;
|
|
3161
|
+
case "parsing":
|
|
3162
|
+
case "uploaded":
|
|
3163
|
+
return `\u25D0 ${status}`;
|
|
3164
|
+
case "error":
|
|
3165
|
+
return "\u2717 error";
|
|
3166
|
+
default:
|
|
3167
|
+
return status;
|
|
3168
|
+
}
|
|
3169
|
+
}
|
|
3170
|
+
function showSOVDetails(s) {
|
|
3171
|
+
console.log();
|
|
3172
|
+
console.log(` File: ${s.fileName}`);
|
|
3173
|
+
console.log(` Status: ${formatSOVStatus(s.status)}`);
|
|
3174
|
+
console.log(` Line: ${s.lineOfBusiness || "property"}`);
|
|
3175
|
+
if (s.effectiveDate) console.log(` Effective: ${formatDate(s.effectiveDate)}`);
|
|
3176
|
+
if (s.expirationDate) console.log(` Expiration: ${formatDate(s.expirationDate)}`);
|
|
3177
|
+
if (s.totalLocations) console.log(` Locations: ${s.totalLocations}`);
|
|
3178
|
+
if (s.totalTIV) console.log(` Total TIV: ${formatCurrency(s.totalTIV)}`);
|
|
3179
|
+
console.log(` Created: ${formatDate(s.createdAt)}`);
|
|
3180
|
+
}
|
|
3181
|
+
|
|
3182
|
+
// src/commands/appetite.ts
|
|
3183
|
+
var import_commander10 = require("commander");
|
|
3184
|
+
var import_ora11 = __toESM(require("ora"));
|
|
3185
|
+
function num(value) {
|
|
3186
|
+
if (value === void 0) return void 0;
|
|
3187
|
+
const n = Number(value);
|
|
3188
|
+
return Number.isFinite(n) ? n : void 0;
|
|
3189
|
+
}
|
|
3190
|
+
function buildMatchInput(options) {
|
|
3191
|
+
const list = (v) => {
|
|
3192
|
+
if (v === void 0) return void 0;
|
|
3193
|
+
const parts = (Array.isArray(v) ? v : [v]).flatMap((p) => p.split(",")).map((p) => p.trim()).filter(Boolean);
|
|
3194
|
+
return parts.length ? parts : void 0;
|
|
3195
|
+
};
|
|
3196
|
+
return {
|
|
3197
|
+
state: String(options.state ?? "").toUpperCase(),
|
|
3198
|
+
industry: options.industry,
|
|
3199
|
+
businessDescription: options.description,
|
|
3200
|
+
naicsCode: options.naics,
|
|
3201
|
+
sicCode: options.sic,
|
|
3202
|
+
coverageTypes: list(options.coverage),
|
|
3203
|
+
carriers: list(options.carriers),
|
|
3204
|
+
marketSize: options.marketSize,
|
|
3205
|
+
annualRevenue: num(options.revenue),
|
|
3206
|
+
annualPayroll: num(options.payroll),
|
|
3207
|
+
propertyValue: num(options.propertyValue),
|
|
3208
|
+
minFit: options.minFit,
|
|
3209
|
+
topK: num(options.top)
|
|
3210
|
+
};
|
|
3211
|
+
}
|
|
3212
|
+
function printResult(result) {
|
|
3213
|
+
if (result.carrierResults.length === 0) {
|
|
3214
|
+
warn("No carrier appetite found for this risk");
|
|
3215
|
+
} else {
|
|
3216
|
+
console.log();
|
|
3217
|
+
table(
|
|
3218
|
+
["Carrier", "Status", "Confidence", "Best class", "Products"],
|
|
3219
|
+
result.carrierResults.map((c) => [
|
|
3220
|
+
c.carrier,
|
|
3221
|
+
c.overallStatus,
|
|
3222
|
+
`${Math.round(c.confidence * 100)}%`,
|
|
3223
|
+
c.bestMatch?.classDescription ?? "-",
|
|
3224
|
+
c.productsAvailable.join(", ")
|
|
3225
|
+
])
|
|
3226
|
+
);
|
|
3227
|
+
}
|
|
3228
|
+
for (const w of result.warnings) warn(w);
|
|
3229
|
+
}
|
|
3230
|
+
function createAppetiteCommand() {
|
|
3231
|
+
const appetite = new import_commander10.Command("appetite").description("Check which carriers will write a risk");
|
|
3232
|
+
appetite.command("match").description("Match a business to carrier appetite").requiredOption("-s, --state <state>", "Two-letter state code").option("-i, --industry <industry>", 'Industry, e.g. "bakery"').option("-d, --description <text>", "What the business does").option("--naics <code>", "NAICS code").option("--sic <code>", "SIC code").option("-c, --coverage <types>", "Comma-separated coverage types, e.g. general_liability,workers_comp").option("--carriers <names>", "Comma-separated carriers to limit the match to").option("--market-size <size>", '"Small Business" or "Middle Market"').option("--revenue <usd>", "Annual revenue").option("--payroll <usd>", "Annual payroll").option("--property-value <usd>", "Total property value").option("--min-fit <fit>", "Raise the quality floor: possible or strong").option("--top <n>", "Maximum matches (1-50)").option("--no-wait", "Return the job id instead of waiting for a cold match").option("--timeout <ms>", "How long to wait for a cold match", "120000").option("--json", "Output as JSON").action(async (options) => {
|
|
3233
|
+
const input = buildMatchInput(options);
|
|
3234
|
+
const timeoutMs = positiveInt(options.timeout, "--timeout");
|
|
3235
|
+
if (!input.industry && !input.businessDescription && !input.naicsCode && !input.sicCode) {
|
|
3236
|
+
error("Describe the business");
|
|
3237
|
+
info("Pass one of --industry, --description, --naics, or --sic");
|
|
3238
|
+
process.exit(1);
|
|
3239
|
+
}
|
|
3240
|
+
const client = getClient();
|
|
3241
|
+
const spinner = (0, import_ora11.default)("Matching appetite...").start();
|
|
3242
|
+
try {
|
|
3243
|
+
const start = await client.appetite.match(input);
|
|
3244
|
+
if (start.status === "succeeded") {
|
|
3245
|
+
spinner.succeed("Match ready (cached)");
|
|
3246
|
+
if (options.json) json(start);
|
|
3247
|
+
else printResult(start);
|
|
3248
|
+
return;
|
|
3249
|
+
}
|
|
3250
|
+
if (!options.wait) {
|
|
3251
|
+
spinner.succeed(`Match queued: ${start.jobId}`);
|
|
3252
|
+
if (options.json) json(start);
|
|
3253
|
+
else info(`Check it with "delegance appetite status ${start.jobId}"`);
|
|
3254
|
+
return;
|
|
3255
|
+
}
|
|
3256
|
+
spinner.text = `Matching appetite (about ${start.estimatedSeconds}s)...`;
|
|
3257
|
+
const done = await client.appetite.waitForMatch(start.jobId, { timeoutMs });
|
|
3258
|
+
spinner.succeed("Match ready");
|
|
3259
|
+
if (options.json) json(done);
|
|
3260
|
+
else printResult(done);
|
|
3261
|
+
} catch (err) {
|
|
3262
|
+
spinner.fail("Appetite match failed");
|
|
3263
|
+
failWith("API error", err);
|
|
3264
|
+
}
|
|
3265
|
+
});
|
|
3266
|
+
appetite.command("status <jobId>").description("Check a queued appetite match").option("--json", "Output as JSON").action(async (jobId, options) => {
|
|
3267
|
+
const client = getClient();
|
|
3268
|
+
const spinner = (0, import_ora11.default)("Fetching match...").start();
|
|
3269
|
+
try {
|
|
3270
|
+
const job = await client.appetite.getMatch(jobId);
|
|
3271
|
+
if (job.status === "succeeded") {
|
|
3272
|
+
spinner.succeed("Match ready");
|
|
3273
|
+
if (options.json) json(job);
|
|
3274
|
+
else printResult(job);
|
|
3275
|
+
} else if (job.status === "failed") {
|
|
3276
|
+
spinner.fail(`Match failed: ${job.errorCode}${job.retryable ? " (retryable)" : ""}`);
|
|
3277
|
+
if (options.json) json(job);
|
|
3278
|
+
process.exit(1);
|
|
3279
|
+
} else {
|
|
3280
|
+
spinner.info(`Match ${job.status}`);
|
|
3281
|
+
if (options.json) json(job);
|
|
3282
|
+
}
|
|
3283
|
+
} catch (err) {
|
|
3284
|
+
spinner.fail("Failed to fetch match");
|
|
3285
|
+
failWith("API error", err);
|
|
3286
|
+
}
|
|
3287
|
+
});
|
|
3288
|
+
return appetite;
|
|
3289
|
+
}
|
|
3290
|
+
|
|
3291
|
+
// src/commands/lead.ts
|
|
3292
|
+
var import_commander11 = require("commander");
|
|
3293
|
+
var import_ora12 = __toESM(require("ora"));
|
|
3294
|
+
function buildLeadInput(options) {
|
|
3295
|
+
const input = {
|
|
3296
|
+
name: options.name ?? "",
|
|
3297
|
+
contactEmail: options.email ?? "",
|
|
3298
|
+
legalName: options.legalName,
|
|
3299
|
+
industry: options.industry,
|
|
3300
|
+
contactPhone: options.phone,
|
|
3301
|
+
website: options.website,
|
|
3302
|
+
companySize: options.size,
|
|
3303
|
+
description: options.description,
|
|
3304
|
+
naicsCode: options.naics,
|
|
3305
|
+
sicCode: options.sic,
|
|
3306
|
+
state: options.state?.toUpperCase(),
|
|
3307
|
+
externalId: options.externalId,
|
|
3308
|
+
source: options.source,
|
|
3309
|
+
notes: options.notes
|
|
3310
|
+
};
|
|
3311
|
+
if (options.street || options.city || options.zip) {
|
|
3312
|
+
input.address = {
|
|
3313
|
+
street: options.street ?? "",
|
|
3314
|
+
suite: options.suite,
|
|
3315
|
+
city: options.city ?? "",
|
|
3316
|
+
state: (options.addressState ?? options.state ?? "").toUpperCase(),
|
|
3317
|
+
zip: options.zip ?? ""
|
|
3318
|
+
};
|
|
3319
|
+
}
|
|
3320
|
+
return input;
|
|
3321
|
+
}
|
|
3322
|
+
function createLeadCommand() {
|
|
3323
|
+
const lead = new import_commander11.Command("lead").alias("leads").description("Send prospect businesses to Delegance");
|
|
3324
|
+
lead.command("create").description("Create a lead (idempotent on --external-id and contact email)").requiredOption("-n, --name <name>", "Business name").requiredOption("-e, --email <email>", "Contact email").option("--legal-name <name>", "Legal name, if different").option("-i, --industry <industry>", "Industry").option("-p, --phone <phone>", "Contact phone").option("-w, --website <url>", "Website (full URL)").option("--size <size>", "Company size").option("-d, --description <text>", "What the business does").option("--naics <code>", "NAICS code").option("--sic <code>", "SIC code").option("-s, --state <state>", "Two-letter state code").option("--street <street>", "Street address").option("--suite <suite>", "Suite").option("--city <city>", "City").option("--address-state <state>", "Address state (defaults to --state)").option("--zip <zip>", "ZIP code").option("--external-id <id>", "Your id for this lead").option("--source <source>", "Lead source label").option("--notes <notes>", "Notes for the broker").option("--json", "Output as JSON").action(async (options) => {
|
|
3325
|
+
const input = buildLeadInput(options);
|
|
3326
|
+
if (input.address && (!input.address.street || !input.address.city || !input.address.state || !input.address.zip)) {
|
|
3327
|
+
error("An address needs --street, --city, --zip, and a state");
|
|
3328
|
+
process.exit(1);
|
|
3329
|
+
}
|
|
3330
|
+
const client = getClient();
|
|
3331
|
+
const spinner = (0, import_ora12.default)("Creating lead...").start();
|
|
3332
|
+
try {
|
|
3333
|
+
const result = await client.leads.create(input);
|
|
3334
|
+
spinner.succeed(result.message);
|
|
3335
|
+
if (options.json) json(result);
|
|
3336
|
+
else keyValue({ "Company ID": result.companyId });
|
|
3337
|
+
} catch (err) {
|
|
3338
|
+
spinner.fail("Failed to create lead");
|
|
3339
|
+
failWith("API error", err);
|
|
3340
|
+
}
|
|
3341
|
+
});
|
|
3342
|
+
return lead;
|
|
3343
|
+
}
|
|
3344
|
+
|
|
3345
|
+
// src/commands/knowledge.ts
|
|
3346
|
+
var import_commander12 = require("commander");
|
|
3347
|
+
var import_ora13 = __toESM(require("ora"));
|
|
3348
|
+
var SEARCH_TYPES = ["notecard", "document_chunk"];
|
|
3349
|
+
function createKnowledgeCommand() {
|
|
3350
|
+
const knowledge = new import_commander12.Command("knowledge").alias("kb").description("Search notecards and document facts");
|
|
3351
|
+
knowledge.command("search <query...>").description("Search notecards and document text").option("-t, --type <type>", `Limit to one result type (${SEARCH_TYPES.join(", ")})`).option("-l, --limit <n>", "Maximum results", "10").option("--json", "Output as JSON").action(async (queryParts, options) => {
|
|
3352
|
+
const client = getClient();
|
|
3353
|
+
const query = queryParts.join(" ");
|
|
3354
|
+
const spinner = (0, import_ora13.default)("Searching...").start();
|
|
3355
|
+
try {
|
|
3356
|
+
const { data } = await client.knowledge.search({
|
|
3357
|
+
query,
|
|
3358
|
+
types: options.type ? [options.type] : void 0,
|
|
3359
|
+
limit: parseInt(options.limit)
|
|
3360
|
+
});
|
|
3361
|
+
spinner.succeed(`Found ${data.length} results`);
|
|
3362
|
+
if (options.json) {
|
|
3363
|
+
json(data);
|
|
3364
|
+
return;
|
|
3365
|
+
}
|
|
3366
|
+
for (const r of data) {
|
|
3367
|
+
console.log();
|
|
3368
|
+
console.log(` ${r.title} (${r.type}, score ${r.score.toFixed(2)})`);
|
|
3369
|
+
if (r.summary) console.log(` ${r.summary}`);
|
|
3370
|
+
for (const h of r.highlights.slice(0, 2)) console.log(` > ${h}`);
|
|
3371
|
+
}
|
|
3372
|
+
} catch (err) {
|
|
3373
|
+
spinner.fail("Search failed");
|
|
3374
|
+
failWith("API error", err);
|
|
3375
|
+
}
|
|
3376
|
+
});
|
|
3377
|
+
knowledge.command("notecards").alias("cards").description("List notecards").option("-t, --type <type>", "Filter by template type").option("-l, --limit <n>", "Maximum results", "25").option("--cursor <cursor>", "Page cursor from a previous list").option("--json", "Output as JSON").action(async (options) => {
|
|
3378
|
+
const client = getClient();
|
|
3379
|
+
const spinner = (0, import_ora13.default)("Fetching notecards...").start();
|
|
3380
|
+
try {
|
|
3381
|
+
const { data, pagination } = await client.knowledge.notecards({
|
|
3382
|
+
type: options.type,
|
|
3383
|
+
limit: parseInt(options.limit),
|
|
3384
|
+
cursor: options.cursor
|
|
3385
|
+
});
|
|
3386
|
+
spinner.succeed(`Found ${data.length} notecards`);
|
|
3387
|
+
if (options.json) {
|
|
3388
|
+
json({ data, pagination });
|
|
3389
|
+
return;
|
|
3390
|
+
}
|
|
3391
|
+
if (data.length > 0) {
|
|
3392
|
+
console.log();
|
|
3393
|
+
table(
|
|
3394
|
+
["ID", "Type", "Title", "Updated"],
|
|
3395
|
+
data.map((n) => [n.id, n.templateType, n.title, formatDate(n.updatedAt ?? n.createdAt)])
|
|
3396
|
+
);
|
|
3397
|
+
}
|
|
3398
|
+
if (pagination.hasMore && pagination.nextCursor) {
|
|
3399
|
+
info(`More: delegance knowledge notecards --cursor ${pagination.nextCursor}`);
|
|
3400
|
+
}
|
|
3401
|
+
} catch (err) {
|
|
3402
|
+
spinner.fail("Failed to list notecards");
|
|
3403
|
+
failWith("API error", err);
|
|
3404
|
+
}
|
|
3405
|
+
});
|
|
3406
|
+
knowledge.command("facts <documentId>").description("Show the structured facts extracted from a document").option("--json", "Output as JSON").action(async (documentId, options) => {
|
|
3407
|
+
const client = getClient();
|
|
3408
|
+
const spinner = (0, import_ora13.default)("Fetching document facts...").start();
|
|
3409
|
+
try {
|
|
3410
|
+
const facts = await client.knowledge.documentFacts(documentId);
|
|
3411
|
+
spinner.stop();
|
|
3412
|
+
if (options.json) {
|
|
3413
|
+
json(facts);
|
|
3414
|
+
return;
|
|
3415
|
+
}
|
|
3416
|
+
header("Document facts");
|
|
3417
|
+
keyValue({ "Carriers": facts.carriers, ...facts.dates });
|
|
3418
|
+
if (facts.policies.length > 0) {
|
|
3419
|
+
console.log();
|
|
3420
|
+
table(
|
|
3421
|
+
["Type", "Policy #", "Carrier", "Effective", "Expires", "Premium"],
|
|
3422
|
+
facts.policies.map((p) => [
|
|
3423
|
+
p.policyType,
|
|
3424
|
+
p.policyNumber ?? "-",
|
|
3425
|
+
p.carrierName ?? "-",
|
|
3426
|
+
p.effectiveDate ?? "-",
|
|
3427
|
+
p.expirationDate ?? "-",
|
|
3428
|
+
p.premium ? formatCurrency(p.premium) : "-"
|
|
3429
|
+
])
|
|
3430
|
+
);
|
|
3431
|
+
}
|
|
3432
|
+
if (facts.coverages.length > 0) {
|
|
3433
|
+
console.log();
|
|
3434
|
+
table(
|
|
3435
|
+
["Coverage", "Limit", "Deductible"],
|
|
3436
|
+
facts.coverages.map((c) => [
|
|
3437
|
+
c.type,
|
|
3438
|
+
c.limit ? formatCurrency(c.limit) : "-",
|
|
3439
|
+
c.deductible ? formatCurrency(c.deductible) : "-"
|
|
3440
|
+
])
|
|
3441
|
+
);
|
|
3442
|
+
}
|
|
3443
|
+
} catch (err) {
|
|
3444
|
+
spinner.fail("Failed to fetch document facts");
|
|
3445
|
+
failWith("API error", err);
|
|
3446
|
+
}
|
|
3447
|
+
});
|
|
3448
|
+
return knowledge;
|
|
3449
|
+
}
|
|
3450
|
+
|
|
3451
|
+
// src/commands/onboarding.ts
|
|
3452
|
+
var import_commander13 = require("commander");
|
|
3453
|
+
var import_ora14 = __toESM(require("ora"));
|
|
3454
|
+
var ONBOARDING_STATES = [
|
|
3455
|
+
"created",
|
|
3456
|
+
"awaiting_verification",
|
|
3457
|
+
"documents_pending",
|
|
3458
|
+
"processing",
|
|
3459
|
+
"ready"
|
|
3460
|
+
];
|
|
3461
|
+
function createOnboardingCommand() {
|
|
3462
|
+
const onboarding = new import_commander13.Command("onboarding").alias("onboard").description("Check account setup and onboarding state");
|
|
3463
|
+
onboarding.command("status").description("Show the setup checklist and the next step").option("--json", "Output as JSON").action(async (options) => {
|
|
3464
|
+
const client = getClient();
|
|
3465
|
+
const spinner = (0, import_ora14.default)("Fetching onboarding status...").start();
|
|
3466
|
+
try {
|
|
3467
|
+
const status = await client.onboarding.status();
|
|
3468
|
+
spinner.stop();
|
|
3469
|
+
if (options.json) {
|
|
3470
|
+
json(status);
|
|
3471
|
+
return;
|
|
3472
|
+
}
|
|
3473
|
+
header("Onboarding");
|
|
3474
|
+
keyValue({
|
|
3475
|
+
"Account": status.account,
|
|
3476
|
+
"Company profile": status.company,
|
|
3477
|
+
"Full profile": status.companyFull,
|
|
3478
|
+
"Billing": status.billing,
|
|
3479
|
+
"Quote ready": status.quoteReady,
|
|
3480
|
+
"Sandbox": status.sandbox
|
|
3481
|
+
});
|
|
3482
|
+
if (status.nextStep) {
|
|
3483
|
+
console.log();
|
|
3484
|
+
info(`Next step: ${status.nextStep.action}`);
|
|
3485
|
+
if (status.nextStep.fields?.length) info(`Missing: ${status.nextStep.fields.join(", ")}`);
|
|
3486
|
+
if (status.nextStep.url) info(status.nextStep.url);
|
|
3487
|
+
}
|
|
3488
|
+
} catch (err) {
|
|
3489
|
+
spinner.fail("Failed to fetch onboarding status");
|
|
3490
|
+
failWith("API error", err);
|
|
3491
|
+
}
|
|
3492
|
+
});
|
|
3493
|
+
onboarding.command("state").description("Show the onboarding state machine position").option("--json", "Output as JSON").action(async (options) => {
|
|
3494
|
+
const client = getClient();
|
|
3495
|
+
const spinner = (0, import_ora14.default)("Fetching onboarding state...").start();
|
|
3496
|
+
try {
|
|
3497
|
+
const state = await client.onboarding.state();
|
|
3498
|
+
spinner.stop();
|
|
3499
|
+
if (options.json) {
|
|
3500
|
+
json(state);
|
|
3501
|
+
return;
|
|
3502
|
+
}
|
|
3503
|
+
header("Onboarding state");
|
|
3504
|
+
keyValue({
|
|
3505
|
+
"State": state.state,
|
|
3506
|
+
"Verified": state.verified,
|
|
3507
|
+
"Verification sent to": state.verification.sentTo,
|
|
3508
|
+
"Documents": `${state.documents.processed}/${state.documents.total} processed`,
|
|
3509
|
+
"Capabilities": state.capabilities
|
|
3510
|
+
});
|
|
3511
|
+
} catch (err) {
|
|
3512
|
+
spinner.fail("Failed to fetch onboarding state");
|
|
3513
|
+
failWith("API error", err);
|
|
3514
|
+
}
|
|
3515
|
+
});
|
|
3516
|
+
onboarding.command("verify").description("Email the account contact a verification link").option("--json", "Output as JSON").action(async (options) => {
|
|
3517
|
+
const client = getClient();
|
|
3518
|
+
const spinner = (0, import_ora14.default)("Requesting verification...").start();
|
|
3519
|
+
try {
|
|
3520
|
+
const result = await client.onboarding.requestVerification();
|
|
3521
|
+
spinner.succeed(`Verification sent to ${result.sentTo}`);
|
|
3522
|
+
if (options.json) json(result);
|
|
3523
|
+
else info('Open the link in that email, then check progress with "delegance onboarding state"');
|
|
3524
|
+
} catch (err) {
|
|
3525
|
+
spinner.fail("Failed to request verification");
|
|
3526
|
+
failWith("API error", err);
|
|
3527
|
+
}
|
|
3528
|
+
});
|
|
3529
|
+
onboarding.command("wait <state>").description(`Wait until onboarding reaches a state (${ONBOARDING_STATES.join(", ")})`).option("--timeout <ms>", "Timeout in milliseconds", "600000").option("--interval <ms>", "Poll interval in milliseconds", "5000").option("--json", "Output as JSON").action(async (target, options) => {
|
|
3530
|
+
if (!ONBOARDING_STATES.includes(target)) {
|
|
3531
|
+
error(`Unknown state "${target}"`);
|
|
3532
|
+
info(`Use one of: ${ONBOARDING_STATES.join(", ")}`);
|
|
3533
|
+
process.exit(1);
|
|
3534
|
+
}
|
|
3535
|
+
const client = getClient();
|
|
3536
|
+
const spinner = (0, import_ora14.default)(`Waiting for ${target}...`).start();
|
|
3537
|
+
try {
|
|
3538
|
+
const state = await client.onboarding.waitForState(target, {
|
|
3539
|
+
timeoutMs: positiveInt(options.timeout, "--timeout"),
|
|
3540
|
+
intervalMs: positiveInt(options.interval, "--interval")
|
|
3541
|
+
});
|
|
3542
|
+
spinner.succeed(`Onboarding reached ${state.state}`);
|
|
3543
|
+
if (options.json) json(state);
|
|
3544
|
+
} catch (err) {
|
|
3545
|
+
spinner.fail(`Did not reach ${target}`);
|
|
3546
|
+
failWith("Wait failed", err);
|
|
3547
|
+
}
|
|
3548
|
+
});
|
|
3549
|
+
return onboarding;
|
|
3550
|
+
}
|
|
3551
|
+
|
|
3552
|
+
// src/commands/usage.ts
|
|
3553
|
+
var import_commander14 = require("commander");
|
|
3554
|
+
var import_ora15 = __toESM(require("ora"));
|
|
3555
|
+
function isoDay(daysAgo, now = /* @__PURE__ */ new Date()) {
|
|
3556
|
+
const d = new Date(now.getTime() - daysAgo * 24 * 60 * 60 * 1e3);
|
|
3557
|
+
return d.toISOString().slice(0, 10);
|
|
3558
|
+
}
|
|
3559
|
+
function cents(n) {
|
|
3560
|
+
return `$${(n / 100).toFixed(2)}`;
|
|
3561
|
+
}
|
|
3562
|
+
function createUsageCommand() {
|
|
3563
|
+
const usage = new import_commander14.Command("usage").description("Show API usage, billing, and rate limits");
|
|
3564
|
+
usage.command("summary").description("Usage for the current billing period").option("--json", "Output as JSON").action(async (options) => {
|
|
3565
|
+
const client = getClient();
|
|
3566
|
+
const spinner = (0, import_ora15.default)("Fetching usage summary...").start();
|
|
3567
|
+
try {
|
|
3568
|
+
const s = await client.usage.summary();
|
|
3569
|
+
spinner.stop();
|
|
3570
|
+
if (options.json) {
|
|
3571
|
+
json(s);
|
|
3572
|
+
return;
|
|
3573
|
+
}
|
|
3574
|
+
header(`Usage for ${s.period}`);
|
|
3575
|
+
keyValue({
|
|
3576
|
+
"Plan": s.plan,
|
|
3577
|
+
"Status": s.status,
|
|
3578
|
+
"Overage": cents(s.totalOverageCents),
|
|
3579
|
+
"As of": formatDate(s.asOf)
|
|
3580
|
+
});
|
|
3581
|
+
const meters = Object.entries(s.meters);
|
|
3582
|
+
if (meters.length > 0) {
|
|
3583
|
+
console.log();
|
|
3584
|
+
table(
|
|
3585
|
+
["Meter", "Used", "Included", "Overage", "Overage cost"],
|
|
3586
|
+
meters.map(([name, m]) => [name, m.used, m.included, m.overage, cents(m.overageCostCents)])
|
|
3587
|
+
);
|
|
3588
|
+
}
|
|
3589
|
+
} catch (err) {
|
|
3590
|
+
spinner.fail("Failed to fetch usage summary");
|
|
3591
|
+
failWith("API error", err);
|
|
3592
|
+
}
|
|
3593
|
+
});
|
|
3594
|
+
usage.command("daily").description("Daily metered usage (default: the last 30 days)").option("--from <date>", "Start date, YYYY-MM-DD (inclusive)").option("--to <date>", "End date, YYYY-MM-DD (inclusive)").option("-m, --meter <meter>", "Only this meter").option("-g, --group-by <group>", "Aggregate by day or meter").option("--json", "Output as JSON").action(async (options) => {
|
|
3595
|
+
const client = getClient();
|
|
3596
|
+
const spinner = (0, import_ora15.default)("Fetching daily usage...").start();
|
|
3597
|
+
try {
|
|
3598
|
+
const { data } = await client.usage.daily({
|
|
3599
|
+
from: options.from ?? isoDay(29),
|
|
3600
|
+
to: options.to ?? isoDay(0),
|
|
3601
|
+
meter: options.meter,
|
|
3602
|
+
groupBy: options.groupBy
|
|
3603
|
+
});
|
|
3604
|
+
spinner.succeed(`${data.length} rows`);
|
|
3605
|
+
if (options.json) {
|
|
3606
|
+
json(data);
|
|
3607
|
+
return;
|
|
3608
|
+
}
|
|
3609
|
+
if (data.length > 0) {
|
|
3610
|
+
console.log();
|
|
3611
|
+
table(["Date", "Meter", "Quantity"], data.map((r) => [r.date, r.meter, r.quantity]));
|
|
3612
|
+
}
|
|
3613
|
+
} catch (err) {
|
|
3614
|
+
spinner.fail("Failed to fetch daily usage");
|
|
3615
|
+
failWith("API error", err);
|
|
3616
|
+
}
|
|
3617
|
+
});
|
|
3618
|
+
usage.command("limits").alias("rate-limits").description("Current rate-limit window").option("--json", "Output as JSON").action(async (options) => {
|
|
3619
|
+
const client = getClient();
|
|
3620
|
+
const spinner = (0, import_ora15.default)("Fetching rate limits...").start();
|
|
3621
|
+
try {
|
|
3622
|
+
const r = await client.usage.rateLimits();
|
|
3623
|
+
spinner.stop();
|
|
3624
|
+
if (options.json) {
|
|
3625
|
+
json(r);
|
|
3626
|
+
return;
|
|
3627
|
+
}
|
|
3628
|
+
header("Rate limits");
|
|
3629
|
+
keyValue({
|
|
3630
|
+
"Per minute": r.requestsPerMinute,
|
|
3631
|
+
"Remaining": r.remaining,
|
|
3632
|
+
"Resets at": formatDate(r.resetsAt)
|
|
3633
|
+
});
|
|
3634
|
+
} catch (err) {
|
|
3635
|
+
spinner.fail("Failed to fetch rate limits");
|
|
3636
|
+
failWith("API error", err);
|
|
3637
|
+
}
|
|
3638
|
+
});
|
|
3639
|
+
return usage;
|
|
3640
|
+
}
|
|
3641
|
+
|
|
3642
|
+
// src/commands/keys.ts
|
|
3643
|
+
var import_commander15 = require("commander");
|
|
3644
|
+
var import_ora16 = __toESM(require("ora"));
|
|
3645
|
+
var import_crypto3 = require("crypto");
|
|
3646
|
+
var import_sdk6 = require("@delegance/sdk");
|
|
3647
|
+
var KEYS_PORTAL_URL = `${PORTAL_URL}/portal/settings/api-keys`;
|
|
3648
|
+
function parseScopes(raw) {
|
|
3649
|
+
const parts = (Array.isArray(raw) ? raw : [raw ?? "read,write"]).flatMap((p) => p.split(/[\s,]+/)).map((p) => p.trim()).filter(Boolean);
|
|
3650
|
+
const unknown = parts.filter((p) => !import_sdk6.API_KEY_SCOPES.includes(p));
|
|
3651
|
+
if (unknown.length > 0) {
|
|
3652
|
+
throw new Error(`Unknown scope(s): ${unknown.join(", ")}. Allowed: ${import_sdk6.API_KEY_SCOPES.join(", ")}`);
|
|
3653
|
+
}
|
|
3654
|
+
return Array.from(new Set(parts));
|
|
3655
|
+
}
|
|
3656
|
+
function replacementExpiryDays(expiresAt, now = /* @__PURE__ */ new Date()) {
|
|
3657
|
+
if (!expiresAt) return void 0;
|
|
3658
|
+
const ms = new Date(expiresAt).getTime() - now.getTime();
|
|
3659
|
+
if (!Number.isFinite(ms)) throw new Error(`Cannot read the old key's expiry "${expiresAt}"; not rotating`);
|
|
3660
|
+
const days = Math.ceil(ms / (24 * 60 * 60 * 1e3));
|
|
3661
|
+
return Math.min(365, Math.max(1, days));
|
|
3662
|
+
}
|
|
3663
|
+
function printCreatedKey(created) {
|
|
3664
|
+
success("API key created");
|
|
3665
|
+
console.log();
|
|
3666
|
+
keyValue({
|
|
3667
|
+
"ID": created.id,
|
|
3668
|
+
"Name": created.name,
|
|
3669
|
+
"Scopes": created.scopes,
|
|
3670
|
+
"Expires": created.expiresAt ? formatDate(created.expiresAt) : "never"
|
|
3671
|
+
});
|
|
3672
|
+
console.log();
|
|
3673
|
+
warn("Save this key now. It is shown once and cannot be retrieved again:");
|
|
3674
|
+
console.log(` ${created.key}`);
|
|
3675
|
+
}
|
|
3676
|
+
function createKeysCommand() {
|
|
3677
|
+
const keys = new import_commander15.Command("keys").alias("api-keys").description("Manage API keys");
|
|
3678
|
+
keys.command("list").alias("ls").description("List API keys for your company").option("-l, --limit <n>", "Maximum results", "50").option("--cursor <cursor>", "Page cursor from a previous list").option("--json", "Output as JSON").action(async (options) => {
|
|
3679
|
+
const client = getClient();
|
|
3680
|
+
const spinner = (0, import_ora16.default)("Fetching API keys...").start();
|
|
3681
|
+
try {
|
|
3682
|
+
const { data, pagination } = await client.apiKeys.list({
|
|
3683
|
+
limit: parseInt(options.limit),
|
|
3684
|
+
cursor: options.cursor
|
|
3685
|
+
});
|
|
3686
|
+
spinner.succeed(`Found ${data.length} keys`);
|
|
3687
|
+
if (options.json) {
|
|
3688
|
+
json({ data, pagination });
|
|
3689
|
+
return;
|
|
3690
|
+
}
|
|
3691
|
+
if (data.length === 0) return;
|
|
3692
|
+
console.log();
|
|
3693
|
+
table(
|
|
3694
|
+
["ID", "Name", "Prefix", "Scopes", "Active", "Last used", "Expires"],
|
|
3695
|
+
data.map((k) => [
|
|
3696
|
+
k.id,
|
|
3697
|
+
k.name,
|
|
3698
|
+
k.prefix,
|
|
3699
|
+
k.scopes.join(","),
|
|
3700
|
+
k.isActive ? "yes" : "no",
|
|
3701
|
+
k.lastUsedAt ? formatDate(k.lastUsedAt) : "-",
|
|
3702
|
+
k.expiresAt ? formatDate(k.expiresAt) : "-"
|
|
3703
|
+
])
|
|
3704
|
+
);
|
|
3705
|
+
if (pagination?.hasMore && pagination.nextCursor) {
|
|
3706
|
+
info(`More keys: delegance keys list --cursor ${pagination.nextCursor}`);
|
|
3707
|
+
}
|
|
3708
|
+
} catch (err) {
|
|
3709
|
+
spinner.fail("Failed to list API keys");
|
|
3710
|
+
failWith("API error", err);
|
|
3711
|
+
}
|
|
3712
|
+
});
|
|
3713
|
+
keys.command("create <name>").description("Create an API key (needs a key with the admin scope)").option("-s, --scopes <scopes>", `Comma-separated scopes (${import_sdk6.API_KEY_SCOPES.join(", ")})`, "read,write").option("-e, --expires-in-days <days>", "Expire the key after N days (1-365)").option("--json", "Output as JSON").action(async (name, options) => {
|
|
3714
|
+
let scopes;
|
|
3715
|
+
try {
|
|
3716
|
+
scopes = parseScopes(options.scopes);
|
|
3717
|
+
} catch (err) {
|
|
3718
|
+
error(err.message);
|
|
3719
|
+
process.exit(1);
|
|
3720
|
+
}
|
|
3721
|
+
const client = getClient();
|
|
3722
|
+
const spinner = (0, import_ora16.default)("Creating API key...").start();
|
|
3723
|
+
try {
|
|
3724
|
+
const created = await client.apiKeys.create(
|
|
3725
|
+
{
|
|
3726
|
+
name,
|
|
3727
|
+
scopes,
|
|
3728
|
+
expiresInDays: options.expiresInDays ? parseInt(options.expiresInDays) : void 0
|
|
3729
|
+
},
|
|
3730
|
+
{ idempotencyKey: (0, import_crypto3.randomUUID)() }
|
|
3731
|
+
);
|
|
3732
|
+
spinner.stop();
|
|
3733
|
+
if (options.json) {
|
|
3734
|
+
json(created);
|
|
3735
|
+
} else {
|
|
3736
|
+
printCreatedKey(created);
|
|
3737
|
+
}
|
|
3738
|
+
} catch (err) {
|
|
3739
|
+
spinner.fail("Failed to create API key");
|
|
3740
|
+
failWith("API error", err);
|
|
3741
|
+
}
|
|
3742
|
+
});
|
|
3743
|
+
keys.command("rotate <keyId>").description("Create a replacement key with the same name and scopes").option("--json", "Output as JSON").action(async (keyId, options) => {
|
|
3744
|
+
const client = getClient();
|
|
3745
|
+
const spinner = (0, import_ora16.default)("Looking up key...").start();
|
|
3746
|
+
try {
|
|
3747
|
+
let cursor;
|
|
3748
|
+
let current2;
|
|
3749
|
+
do {
|
|
3750
|
+
const page = await client.apiKeys.list({ limit: 100, cursor });
|
|
3751
|
+
current2 = page.data.find((k) => k.id === keyId);
|
|
3752
|
+
cursor = page.pagination?.hasMore ? page.pagination.nextCursor : void 0;
|
|
3753
|
+
} while (!current2 && cursor);
|
|
3754
|
+
if (!current2) {
|
|
3755
|
+
spinner.fail(`No API key with id ${keyId}`);
|
|
3756
|
+
process.exit(1);
|
|
3757
|
+
}
|
|
3758
|
+
spinner.text = "Creating replacement key...";
|
|
3759
|
+
const created = await client.apiKeys.create(
|
|
3760
|
+
{
|
|
3761
|
+
name: `${current2.name} (rotated)`,
|
|
3762
|
+
scopes: current2.scopes,
|
|
3763
|
+
expiresInDays: replacementExpiryDays(current2.expiresAt)
|
|
3764
|
+
},
|
|
3765
|
+
{ idempotencyKey: (0, import_crypto3.randomUUID)() }
|
|
3766
|
+
);
|
|
3767
|
+
spinner.stop();
|
|
3768
|
+
if (options.json) {
|
|
3769
|
+
json({ newKey: created, oldKeyId: keyId, oldKeyStillActive: true });
|
|
3770
|
+
return;
|
|
3771
|
+
}
|
|
3772
|
+
printCreatedKey(created);
|
|
3773
|
+
console.log();
|
|
3774
|
+
warn(`The old key ${current2.prefix} is still active.`);
|
|
3775
|
+
info(`Switch your integrations to the new key, then revoke the old one at ${KEYS_PORTAL_URL}`);
|
|
3776
|
+
} catch (err) {
|
|
3777
|
+
spinner.fail("Failed to rotate API key");
|
|
3778
|
+
failWith("API error", err);
|
|
3779
|
+
}
|
|
3780
|
+
});
|
|
3781
|
+
keys.command("revoke <keyId>").description("Revoke an API key").action(async (keyId) => {
|
|
3782
|
+
const client = getClient();
|
|
3783
|
+
const spinner = (0, import_ora16.default)("Revoking API key...").start();
|
|
3784
|
+
try {
|
|
3785
|
+
await client.apiKeys.revoke(keyId);
|
|
3786
|
+
spinner.succeed("API key revoked");
|
|
3787
|
+
} catch (err) {
|
|
3788
|
+
spinner.fail("Failed to revoke API key");
|
|
3789
|
+
if (err instanceof import_sdk6.AuthenticationError) {
|
|
3790
|
+
error("Revoking a key needs a signed-in portal session today");
|
|
3791
|
+
info(`Revoke it at ${KEYS_PORTAL_URL}`);
|
|
3792
|
+
process.exit(1);
|
|
3793
|
+
}
|
|
3794
|
+
failWith("API error", err);
|
|
3795
|
+
}
|
|
3796
|
+
});
|
|
3797
|
+
return keys;
|
|
3798
|
+
}
|
|
3799
|
+
|
|
3800
|
+
// src/commands/config.ts
|
|
3801
|
+
var import_commander16 = require("commander");
|
|
3802
|
+
var import_inquirer9 = __toESM(require("inquirer"));
|
|
3803
|
+
function createConfigCommand() {
|
|
3804
|
+
const config2 = new import_commander16.Command("config").description("Manage CLI configuration");
|
|
3805
|
+
config2.command("show").alias("list").description("Show current configuration").option("--json", "Output as JSON").action((options) => {
|
|
3806
|
+
const currentConfig = getConfig();
|
|
3807
|
+
const apiKey = getApiKey();
|
|
3808
|
+
const baseUrl = getBaseUrl();
|
|
3809
|
+
if (options.json) {
|
|
3810
|
+
console.log(JSON.stringify({
|
|
3811
|
+
baseUrl,
|
|
3812
|
+
defaultCompany: currentConfig.get("defaultCompany"),
|
|
3813
|
+
hasApiKey: !!apiKey,
|
|
3814
|
+
apiKeyPrefix: apiKey ? apiKey.substring(0, 12) + "..." : null
|
|
3815
|
+
}, null, 2));
|
|
3816
|
+
} else {
|
|
3817
|
+
header("Current Configuration");
|
|
3818
|
+
console.log();
|
|
3819
|
+
keyValue("Base URL", baseUrl);
|
|
3820
|
+
keyValue("API Key", apiKey ? apiKey.substring(0, 12) + "..." + apiKey.slice(-4) : "Not set");
|
|
3821
|
+
keyValue("Default Company", currentConfig.get("defaultCompany") || "Not set");
|
|
3822
|
+
console.log();
|
|
3823
|
+
info(`Config file: ${currentConfig.path}`);
|
|
3824
|
+
}
|
|
3825
|
+
});
|
|
3826
|
+
config2.command("set <key> <value>").description("Set a configuration value").action((key, value) => {
|
|
3827
|
+
const currentConfig = getConfig();
|
|
3828
|
+
const validKeys = ["baseUrl", "defaultCompany"];
|
|
3829
|
+
if (!validKeys.includes(key)) {
|
|
3830
|
+
error(`Unknown config key: ${key}`);
|
|
3831
|
+
info(`Valid keys: ${validKeys.join(", ")}`);
|
|
3832
|
+
process.exit(1);
|
|
3833
|
+
}
|
|
3834
|
+
currentConfig.set(key, value);
|
|
3835
|
+
success(`Set ${key} = ${value}`);
|
|
3836
|
+
});
|
|
3837
|
+
config2.command("get <key>").description("Get a configuration value").action((key) => {
|
|
3838
|
+
const currentConfig = getConfig();
|
|
3839
|
+
const value = currentConfig.get(key);
|
|
3840
|
+
if (value === void 0) {
|
|
3841
|
+
info(`${key} is not set`);
|
|
3842
|
+
} else {
|
|
3843
|
+
console.log(value);
|
|
3844
|
+
}
|
|
3845
|
+
});
|
|
3846
|
+
config2.command("reset").description("Reset all configuration to defaults").option("-f, --force", "Skip confirmation").action(async (options) => {
|
|
3847
|
+
if (!options.force) {
|
|
3848
|
+
const { confirm } = await import_inquirer9.default.prompt([
|
|
3849
|
+
{
|
|
3850
|
+
type: "confirm",
|
|
3851
|
+
name: "confirm",
|
|
3852
|
+
message: "This will reset all configuration. Continue?",
|
|
3853
|
+
default: false
|
|
3854
|
+
}
|
|
3855
|
+
]);
|
|
3856
|
+
if (!confirm) {
|
|
3857
|
+
info("Cancelled");
|
|
3858
|
+
return;
|
|
3859
|
+
}
|
|
3860
|
+
}
|
|
3861
|
+
const currentConfig = getConfig();
|
|
3862
|
+
currentConfig.clear();
|
|
3863
|
+
clearCredentials();
|
|
3864
|
+
success("Configuration reset to defaults");
|
|
3865
|
+
info('You will need to run "delegance auth login" again');
|
|
3866
|
+
});
|
|
3867
|
+
config2.command("env").description("Show environment information").action(() => {
|
|
3868
|
+
header("Environment");
|
|
3869
|
+
console.log();
|
|
3870
|
+
keyValue("Node.js", process.version);
|
|
3871
|
+
keyValue("Platform", process.platform);
|
|
3872
|
+
keyValue("Arch", process.arch);
|
|
3873
|
+
keyValue("CWD", process.cwd());
|
|
3874
|
+
console.log();
|
|
3875
|
+
header("Environment Variables");
|
|
3876
|
+
console.log();
|
|
3877
|
+
keyValue("DELEGANCE_API_KEY", process.env.DELEGANCE_API_KEY ? "(set)" : "(not set)");
|
|
3878
|
+
keyValue("DELEGANCE_BASE_URL", process.env.DELEGANCE_BASE_URL || "(not set)");
|
|
3879
|
+
keyValue("DEBUG", process.env.DEBUG || "(not set)");
|
|
3880
|
+
});
|
|
3881
|
+
config2.command("profiles").description("List available profiles").action(() => {
|
|
3882
|
+
const currentConfig = getConfig();
|
|
3883
|
+
const profiles = currentConfig.get("profiles") || {};
|
|
3884
|
+
const activeProfile = currentConfig.get("activeProfile") || "default";
|
|
3885
|
+
header("Profiles");
|
|
3886
|
+
console.log();
|
|
3887
|
+
if (Object.keys(profiles).length === 0) {
|
|
3888
|
+
info("No profiles configured");
|
|
3889
|
+
info("Profiles allow you to switch between different accounts/environments");
|
|
3890
|
+
console.log();
|
|
3891
|
+
info("Example: delegance config set-profile production --url https://portal.delegancebrokerage.com");
|
|
3892
|
+
} else {
|
|
3893
|
+
for (const [name, profile] of Object.entries(profiles)) {
|
|
3894
|
+
const isActive = name === activeProfile;
|
|
3895
|
+
console.log(` ${isActive ? "\u2192 " : " "}${name}${isActive ? " (active)" : ""}`);
|
|
3896
|
+
if (typeof profile === "object" && profile !== null) {
|
|
3897
|
+
console.log(` URL: ${profile.baseUrl || "default"}`);
|
|
3898
|
+
}
|
|
3899
|
+
}
|
|
3900
|
+
}
|
|
3901
|
+
});
|
|
3902
|
+
config2.command("use-profile <name>").description("Switch to a profile").action((name) => {
|
|
3903
|
+
const currentConfig = getConfig();
|
|
3904
|
+
const profiles = currentConfig.get("profiles") || {};
|
|
3905
|
+
if (!profiles[name] && name !== "default") {
|
|
3906
|
+
error(`Profile "${name}" not found`);
|
|
3907
|
+
info("Create it with: delegance config set-profile <name> --url <base-url>");
|
|
3908
|
+
process.exit(1);
|
|
3909
|
+
}
|
|
3910
|
+
currentConfig.set("activeProfile", name);
|
|
3911
|
+
success(`Switched to profile: ${name}`);
|
|
3912
|
+
if (name !== "default" && profiles[name]?.baseUrl) {
|
|
3913
|
+
info(`Base URL: ${profiles[name].baseUrl}`);
|
|
3914
|
+
}
|
|
3915
|
+
});
|
|
3916
|
+
config2.command("set-profile <name>").description("Create or update a profile").option("-u, --url <url>", "Base URL for this profile").action((name, options) => {
|
|
3917
|
+
const currentConfig = getConfig();
|
|
3918
|
+
const profiles = currentConfig.get("profiles") || {};
|
|
3919
|
+
profiles[name] = {
|
|
3920
|
+
...profiles[name],
|
|
3921
|
+
baseUrl: options.url || profiles[name]?.baseUrl
|
|
3922
|
+
};
|
|
3923
|
+
currentConfig.set("profiles", profiles);
|
|
3924
|
+
success(`Profile "${name}" ${profiles[name] ? "updated" : "created"}`);
|
|
3925
|
+
if (options.url) {
|
|
3926
|
+
info(`Base URL: ${options.url}`);
|
|
3927
|
+
}
|
|
3928
|
+
});
|
|
3929
|
+
return config2;
|
|
3930
|
+
}
|
|
3931
|
+
|
|
3932
|
+
// src/program.ts
|
|
3933
|
+
var import_commander19 = require("commander");
|
|
3934
|
+
|
|
3935
|
+
// src/commands/feedback.ts
|
|
3936
|
+
var import_commander17 = require("commander");
|
|
3937
|
+
var import_inquirer10 = __toESM(require("inquirer"));
|
|
3938
|
+
var import_sdk7 = require("@delegance/sdk");
|
|
3939
|
+
var TYPES = ["bug", "idea", "question"];
|
|
3940
|
+
var MESSAGE_MAX = 500;
|
|
3941
|
+
var USAGE = 'delegance feedback "what happened" [--type bug|idea|question] [--include-last]';
|
|
3942
|
+
function feedbackInputMode(o) {
|
|
3943
|
+
if (o.message && o.message.trim()) return "given";
|
|
3944
|
+
return o.isTTY ? "prompt" : "usage_error";
|
|
3945
|
+
}
|
|
3946
|
+
function buildFeedbackContext(last) {
|
|
3947
|
+
const ctx = {};
|
|
3948
|
+
if (last) {
|
|
3949
|
+
ctx.command = last.command;
|
|
3950
|
+
ctx.exitCode = last.exitCode;
|
|
3951
|
+
if (last.traceId) ctx.traceId = last.traceId;
|
|
3952
|
+
ctx.at = last.at;
|
|
3953
|
+
}
|
|
3954
|
+
ctx.cliVersion = CLI_VERSION;
|
|
3955
|
+
ctx.sdkVersion = import_sdk7.SDK_VERSION;
|
|
3956
|
+
return ctx;
|
|
3957
|
+
}
|
|
3958
|
+
function usageError(message) {
|
|
3959
|
+
error(message);
|
|
3960
|
+
info(`Usage: ${USAGE}`);
|
|
3961
|
+
process.exit(2);
|
|
3962
|
+
}
|
|
3963
|
+
function createFeedbackCommand() {
|
|
3964
|
+
return new import_commander17.Command("feedback").alias("bug").description("Report a bug, an idea, or a question to Delegance").argument("[message...]", "What happened (asked for when left out on a terminal)").option("-t, --type <type>", "bug, idea, or question", "bug").option("--include-last", "Attach the last command name, exit code, trace id, and CLI/SDK versions (never its arguments)").option("--json", "Output as JSON").action(async (words, options) => {
|
|
3965
|
+
const type = String(options.type ?? "bug").toLowerCase();
|
|
3966
|
+
if (!TYPES.includes(type)) usageError(`--type must be bug, idea, or question, got "${options.type}"`);
|
|
3967
|
+
let message = (words ?? []).join(" ").trim();
|
|
3968
|
+
const mode = feedbackInputMode({ message, isTTY: Boolean(process.stdin.isTTY && process.stdout.isTTY) });
|
|
3969
|
+
if (mode === "usage_error") usageError(`Say what happened: ${USAGE}`);
|
|
3970
|
+
if (mode === "prompt") {
|
|
3971
|
+
const answer = await import_inquirer10.default.prompt([
|
|
3972
|
+
{ type: "input", name: "message", message: type === "bug" ? "What went wrong?" : "What would you like to tell us?" }
|
|
3973
|
+
]);
|
|
3974
|
+
message = String(answer.message ?? "").trim();
|
|
3975
|
+
if (!message) usageError("Nothing sent: the message was empty.");
|
|
3976
|
+
}
|
|
3977
|
+
if (message.length > MESSAGE_MAX) usageError(`Keep the message to ${MESSAGE_MAX} characters or fewer (it has ${message.length}).`);
|
|
3978
|
+
const context = options.includeLast ? buildFeedbackContext(readLastCommand()) : void 0;
|
|
3979
|
+
try {
|
|
3980
|
+
const { id } = await getClient().feedback.submit({ type, message, context });
|
|
3981
|
+
if (options.json) {
|
|
3982
|
+
json({ id });
|
|
3983
|
+
return;
|
|
3984
|
+
}
|
|
3985
|
+
success(`Sent. Reference: ${id}`);
|
|
3986
|
+
} catch (err) {
|
|
3987
|
+
failWith("Could not send the report", err);
|
|
3988
|
+
}
|
|
3989
|
+
});
|
|
3990
|
+
}
|
|
3991
|
+
|
|
3992
|
+
// src/commands/commerce.ts
|
|
3993
|
+
var import_commander18 = require("commander");
|
|
3994
|
+
var fs7 = __toESM(require("fs"));
|
|
3995
|
+
var import_inquirer11 = __toESM(require("inquirer"));
|
|
3996
|
+
var import_ora17 = __toESM(require("ora"));
|
|
3997
|
+
var import_sdk8 = require("@delegance/sdk");
|
|
3998
|
+
function formatCents(cents2, currency = "usd") {
|
|
3999
|
+
return currency.toLowerCase() === "usd" ? formatCurrency(cents2 / 100) : `${(cents2 / 100).toFixed(2)} ${currency.toUpperCase()}`;
|
|
4000
|
+
}
|
|
4001
|
+
function readJsonFile(file) {
|
|
4002
|
+
try {
|
|
4003
|
+
const parsed = JSON.parse(fs7.readFileSync(file, "utf8"));
|
|
4004
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("expected a JSON object");
|
|
4005
|
+
return parsed;
|
|
4006
|
+
} catch (e) {
|
|
4007
|
+
error(`Could not read ${file}: ${e.message}`);
|
|
4008
|
+
process.exit(1);
|
|
4009
|
+
}
|
|
4010
|
+
}
|
|
4011
|
+
function describeBindResult(r) {
|
|
4012
|
+
const lines = [];
|
|
4013
|
+
if (r.tier === "A" && r.checkoutUrl) {
|
|
4014
|
+
lines.push(`Checkout opened with ${r.carrierName}. Pay on their page to bind:`);
|
|
4015
|
+
lines.push(r.checkoutUrl);
|
|
4016
|
+
lines.push("You'll get the policy by email; we attach it to your account automatically. It is not bound until the carrier confirms.");
|
|
4017
|
+
return lines;
|
|
4018
|
+
}
|
|
4019
|
+
if (r.tier === "C" && r.reasons?.length) lines.push(`A broker reviews this one (${r.reasons.map((x) => x.replace(/_/g, " ")).join(", ")}).`);
|
|
4020
|
+
lines.push(r.next);
|
|
4021
|
+
return lines;
|
|
4022
|
+
}
|
|
4023
|
+
async function askCode() {
|
|
4024
|
+
if (!process.stdin.isTTY) return null;
|
|
4025
|
+
const answer = await import_inquirer11.default.prompt([{
|
|
4026
|
+
type: "input",
|
|
4027
|
+
name: "code",
|
|
4028
|
+
message: "Code from your email:",
|
|
4029
|
+
validate: (v) => /^\d{6}$/.test(v.trim()) || "Enter the 6 digits from the email"
|
|
4030
|
+
}]);
|
|
4031
|
+
return String(answer.code).trim();
|
|
4032
|
+
}
|
|
4033
|
+
function createBindCommand() {
|
|
4034
|
+
return new import_commander18.Command("bind").description('Bind one offer from "quote status": the carrier checkout when the carrier offers it, otherwise a broker review').argument("<submissionId>", "The quote submission id").argument("<offerId>", "The offer id from the quote table").option("--attest", "Confirm the application answers are accurate (asked interactively when omitted)").option("--step-up <id>", "Finish an earlier attempt: the stepUpId the code email belongs to").option("--code <code>", "The 6-digit code from the email").option("--json", "Output as JSON").action(async (submissionId, offerId, options) => {
|
|
4035
|
+
const client = getClient();
|
|
4036
|
+
let attest = options.attest === true;
|
|
4037
|
+
if (!attest && process.stdin.isTTY && !options.json) {
|
|
4038
|
+
const a = await import_inquirer11.default.prompt([{
|
|
4039
|
+
type: "confirm",
|
|
4040
|
+
name: "ok",
|
|
4041
|
+
message: "Are the answers on this application accurate? Binding relies on them.",
|
|
4042
|
+
default: false
|
|
4043
|
+
}]);
|
|
4044
|
+
attest = a.ok === true;
|
|
4045
|
+
if (!attest) {
|
|
4046
|
+
warn('Not bound. Fix the answers with "delegance quote start", then bind again.');
|
|
4047
|
+
return;
|
|
4048
|
+
}
|
|
4049
|
+
}
|
|
4050
|
+
const spinner = options.json ? null : (0, import_ora17.default)("Requesting the bind...").start();
|
|
4051
|
+
try {
|
|
4052
|
+
let r = await client.instantQuotes.bindWithStepUp(submissionId, offerId, {
|
|
4053
|
+
...attest ? { attest: true } : {},
|
|
4054
|
+
...options.stepUp && options.code ? { stepUpId: String(options.stepUp), code: String(options.code) } : {}
|
|
4055
|
+
});
|
|
4056
|
+
spinner?.stop();
|
|
4057
|
+
if ("stepUpRequired" in r) {
|
|
4058
|
+
const stepUpId = r.stepUpId;
|
|
4059
|
+
info(`We emailed a 6-digit code to ${r.sentTo}. It expires in 10 minutes.`);
|
|
4060
|
+
const code = options.code ? String(options.code) : await askCode();
|
|
4061
|
+
if (!code) {
|
|
4062
|
+
warn(`Finish with: delegance bind ${submissionId} ${offerId} --attest --step-up ${stepUpId} --code <code>`);
|
|
4063
|
+
return;
|
|
4064
|
+
}
|
|
4065
|
+
r = await client.instantQuotes.bindWithStepUp(submissionId, offerId, { ...attest ? { attest: true } : {}, stepUpId, code });
|
|
4066
|
+
if ("stepUpRequired" in r) {
|
|
4067
|
+
error("The code was not accepted. Run the command again for a new code.");
|
|
4068
|
+
process.exit(1);
|
|
4069
|
+
}
|
|
4070
|
+
}
|
|
4071
|
+
if (options.json) {
|
|
4072
|
+
json(r);
|
|
4073
|
+
return;
|
|
4074
|
+
}
|
|
4075
|
+
if (r.existing) warn(`A bind for this coverage is already open (${r.carrierName}). Showing it.`);
|
|
4076
|
+
else if (r.tier !== "A") success(`Bind requested: ${r.carrierName} ${r.coverage}${r.premium === null ? "" : ` at ${formatCurrency(r.premium)}/yr`}`);
|
|
4077
|
+
keyValue({ "Bind request": r.bindRequestId, Status: r.status });
|
|
4078
|
+
describeBindResult(r).forEach((l) => info(l));
|
|
4079
|
+
} catch (err) {
|
|
4080
|
+
spinner?.stop();
|
|
4081
|
+
if (err instanceof import_sdk8.DeleganceError && [400, 403, 409].includes(err.statusCode ?? 0)) {
|
|
4082
|
+
error(err.message);
|
|
4083
|
+
process.exit(1);
|
|
4084
|
+
}
|
|
4085
|
+
failWith("Bind failed", err);
|
|
4086
|
+
}
|
|
4087
|
+
});
|
|
4088
|
+
}
|
|
4089
|
+
function summarizeBilling(payments) {
|
|
4090
|
+
let openCents = 0;
|
|
4091
|
+
let paidCents = 0;
|
|
4092
|
+
let openCount = 0;
|
|
4093
|
+
for (const p of payments) {
|
|
4094
|
+
if (p.payable) {
|
|
4095
|
+
openCents += p.amount;
|
|
4096
|
+
openCount += 1;
|
|
4097
|
+
} else if (["paid", "succeeded", "captured"].includes(p.status)) paidCents += p.amount;
|
|
4098
|
+
}
|
|
4099
|
+
return { openCents, paidCents, openCount };
|
|
4100
|
+
}
|
|
4101
|
+
function createBillingCommand() {
|
|
4102
|
+
return new import_commander18.Command("billing").description("What you owe and what you paid: every premium payment request on the account").option("--json", "Output as JSON").action(async (options) => {
|
|
4103
|
+
try {
|
|
4104
|
+
const payments = await getClient().payments.list();
|
|
4105
|
+
const sum = summarizeBilling(payments);
|
|
4106
|
+
if (options.json) {
|
|
4107
|
+
json({ ...sum, payments });
|
|
4108
|
+
return;
|
|
4109
|
+
}
|
|
4110
|
+
if (payments.length === 0) {
|
|
4111
|
+
info("No payment requests yet. Carrier-billed policies are invoiced by the carrier directly.");
|
|
4112
|
+
return;
|
|
4113
|
+
}
|
|
4114
|
+
table(
|
|
4115
|
+
["Payment", "Amount", "Status", "Due", "For"],
|
|
4116
|
+
payments.map((p) => [p.id, formatCents(p.amount, p.currency), p.status, p.dueDate ? formatDate(p.dueDate) : "-", p.description ?? "-"])
|
|
4117
|
+
);
|
|
4118
|
+
keyValue({ "Open": `${formatCents(sum.openCents)} (${sum.openCount})`, "Paid": formatCents(sum.paidCents) });
|
|
4119
|
+
if (sum.openCount > 0) info("Pay one with: delegance pay <paymentId>");
|
|
4120
|
+
} catch (err) {
|
|
4121
|
+
failWith("API error", err);
|
|
4122
|
+
}
|
|
4123
|
+
});
|
|
4124
|
+
}
|
|
4125
|
+
function createPayCommand() {
|
|
4126
|
+
return new import_commander18.Command("pay").description("Pay a premium: list payment requests, or get a checkout link for one").argument("[paymentId]", "Payment request to pay (omit to list them)").option("--json", "Output as JSON").action(async (paymentId, options) => {
|
|
4127
|
+
const client = getClient();
|
|
4128
|
+
try {
|
|
4129
|
+
if (!paymentId) {
|
|
4130
|
+
const payments = await client.payments.list();
|
|
4131
|
+
if (options.json) {
|
|
4132
|
+
json(payments);
|
|
4133
|
+
return;
|
|
4134
|
+
}
|
|
4135
|
+
if (payments.length === 0) {
|
|
4136
|
+
info("No payment requests yet. One appears after a broker reviews your bind.");
|
|
4137
|
+
return;
|
|
4138
|
+
}
|
|
4139
|
+
table(
|
|
4140
|
+
["Payment id", "Amount", "Description", "Status", "Due"],
|
|
4141
|
+
payments.map((p) => [p.id, formatCents(p.amount, p.currency), p.description ?? "", p.payable ? `${p.status} (payable)` : p.status, p.dueDate ? formatDate(p.dueDate) : "-"])
|
|
4142
|
+
);
|
|
4143
|
+
const next = payments.find((p) => p.payable);
|
|
4144
|
+
if (next) info(`Pay it: delegance pay ${next.id}`);
|
|
4145
|
+
return;
|
|
4146
|
+
}
|
|
4147
|
+
const { checkoutUrl } = await client.payments.checkout(paymentId);
|
|
4148
|
+
if (options.json) json({ checkoutUrl });
|
|
4149
|
+
else {
|
|
4150
|
+
success("Open this link to pay with Stripe:");
|
|
4151
|
+
console.log(checkoutUrl);
|
|
4152
|
+
}
|
|
4153
|
+
} catch (err) {
|
|
4154
|
+
failWith("Payment failed", err);
|
|
4155
|
+
}
|
|
4156
|
+
});
|
|
4157
|
+
}
|
|
4158
|
+
function shellQuote(v) {
|
|
4159
|
+
return `'${v.replace(/'/g, `'\\''`)}'`;
|
|
4160
|
+
}
|
|
4161
|
+
function bondStartLines(r, ctx = {}) {
|
|
4162
|
+
const out = [];
|
|
4163
|
+
const str = (v) => v === void 0 || v === null ? "" : String(v);
|
|
4164
|
+
if (r.session_id) {
|
|
4165
|
+
out.push(`Bond quote started: ${str(r.session_id)}${r.bond_name ? ` (${str(r.bond_name)})` : ""}`);
|
|
4166
|
+
const qs = Array.isArray(r.questions) ? r.questions : [];
|
|
4167
|
+
if (qs.length) {
|
|
4168
|
+
out.push("Questions (answer by key in a JSON file):");
|
|
4169
|
+
qs.forEach((q) => {
|
|
4170
|
+
const opts = Array.isArray(q.options) && q.options.length ? ` [${q.options.map(str).join(" | ")}]` : "";
|
|
4171
|
+
out.push(` ${str(q.key)}: ${str(q.label)}${q.required ? " (required)" : ""}${opts}`);
|
|
4172
|
+
});
|
|
4173
|
+
}
|
|
4174
|
+
out.push(`Answer with: delegance bond answer ${str(r.session_id)} --answers answers.json`);
|
|
4175
|
+
return out;
|
|
4176
|
+
}
|
|
4177
|
+
if (Array.isArray(r.candidates) && r.candidates.length) {
|
|
4178
|
+
out.push("That name fits more than one bond. Rerun with the exact name:");
|
|
4179
|
+
const rawState = (ctx.state ?? "").trim();
|
|
4180
|
+
const state = !rawState ? null : /^[A-Za-z]{2}$/.test(rawState) ? rawState.toUpperCase() : shellQuote(rawState);
|
|
4181
|
+
r.candidates.forEach((name) => out.push(state ? ` delegance bond quote --state ${state} --bond ${shellQuote(str(name))}` : ` --bond ${shellQuote(str(name))}`));
|
|
4182
|
+
return out;
|
|
4183
|
+
}
|
|
4184
|
+
if (r.needsAmount) {
|
|
4185
|
+
out.push("This is a contract bond. Rerun with the bond amount in dollars: --bond-amount <amount>");
|
|
4186
|
+
return out;
|
|
4187
|
+
}
|
|
4188
|
+
if (r.route === "underwriting_submission") {
|
|
4189
|
+
out.push(`At ${str(r.amount)} this is above the online limit of ${str(r.instantIssueLimit)}.`);
|
|
4190
|
+
out.push("A licensed Delegance broker submits it to surety markets. Underwriting will need:");
|
|
4191
|
+
(Array.isArray(r.submissionRequirements) ? r.submissionRequirements : []).forEach((item) => out.push(` - ${str(item)}`));
|
|
4192
|
+
return out;
|
|
4193
|
+
}
|
|
4194
|
+
if (r.applyUrl) {
|
|
4195
|
+
out.push("This bond is not quoted in the terminal yet. Apply with the surety directly:");
|
|
4196
|
+
out.push(` ${str(r.applyUrl)}`);
|
|
4197
|
+
return out;
|
|
4198
|
+
}
|
|
4199
|
+
const matches = Array.isArray(r.matches) ? r.matches : [];
|
|
4200
|
+
if (matches.length) {
|
|
4201
|
+
out.push("Bonds that match, with their apply links:");
|
|
4202
|
+
matches.forEach((m) => out.push(` ${str(m.name)}
|
|
4203
|
+
${str(m.applyUrl)}`));
|
|
4204
|
+
return out;
|
|
4205
|
+
}
|
|
4206
|
+
out.push("No bond matched that name. Browse every bond by state:");
|
|
4207
|
+
out.push(` ${str(r.filteredUrl ?? r.stateUrl ?? r.finderUrl)}`);
|
|
4208
|
+
return out;
|
|
4209
|
+
}
|
|
4210
|
+
function createBondCommand() {
|
|
4211
|
+
const bond = new import_commander18.Command("bond").alias("bonds").description("Surety bond quotes");
|
|
4212
|
+
bond.command("quote").description("Start a bond quote and list its questions").requiredOption("-s, --state <state>", "Two-letter state, e.g. TX").option("-b, --bond <name>", 'Bond name or keyword, e.g. "notary", "contractor license"').option("--entity-id <id>", "Exact bond id, when you have it").option("--contract-amount <amount>", "Contract value, for contract bonds").option("--bond-amount <amount>", "Bond penal sum").option("--json", "Output as JSON").action(async (options) => {
|
|
4213
|
+
try {
|
|
4214
|
+
const r = await getClient().bonds.start({
|
|
4215
|
+
state: String(options.state).toUpperCase(),
|
|
4216
|
+
bondName: options.bond,
|
|
4217
|
+
entityId: options.entityId,
|
|
4218
|
+
contractAmount: options.contractAmount,
|
|
4219
|
+
bondAmount: options.bondAmount
|
|
4220
|
+
});
|
|
4221
|
+
if (options.json) {
|
|
4222
|
+
json(r);
|
|
4223
|
+
return;
|
|
4224
|
+
}
|
|
4225
|
+
bondStartLines(r, { state: String(options.state).toUpperCase() }).forEach((line) => console.log(line));
|
|
4226
|
+
} catch (err) {
|
|
4227
|
+
failWith("Bond quote failed", err);
|
|
4228
|
+
}
|
|
4229
|
+
});
|
|
4230
|
+
bond.command("answer <sessionId>").description("Submit answers for a bond quote (starts pricing when complete)").requiredOption("-a, --answers <file>", "JSON file of answers keyed by question id").option("--json", "Output as JSON").action(async (sessionId, options) => {
|
|
4231
|
+
try {
|
|
4232
|
+
const r = await getClient().bonds.answer(sessionId, readJsonFile(options.answers));
|
|
4233
|
+
if (options.json) json(r);
|
|
4234
|
+
else {
|
|
4235
|
+
console.log(JSON.stringify(r, null, 2));
|
|
4236
|
+
info(`Check it: delegance bond status ${sessionId}`);
|
|
4237
|
+
}
|
|
4238
|
+
} catch (err) {
|
|
4239
|
+
failWith("Could not submit the answers", err);
|
|
4240
|
+
}
|
|
4241
|
+
});
|
|
4242
|
+
bond.command("status <sessionId>").description("Bond premium and the surety apply link").option("--json", "Output as JSON").action(async (sessionId, options) => {
|
|
4243
|
+
try {
|
|
4244
|
+
const r = await getClient().bonds.status(sessionId);
|
|
4245
|
+
if (options.json) json(r);
|
|
4246
|
+
else {
|
|
4247
|
+
console.log(JSON.stringify(r, null, 2));
|
|
4248
|
+
if (r.apply_url) info(`Buy it on the surety's page: ${String(r.apply_url)}`);
|
|
4249
|
+
}
|
|
4250
|
+
} catch (err) {
|
|
4251
|
+
failWith("Could not read the bond quote", err);
|
|
4252
|
+
}
|
|
4253
|
+
});
|
|
4254
|
+
return bond;
|
|
4255
|
+
}
|
|
4256
|
+
function createClaimCommand() {
|
|
4257
|
+
const claim = new import_commander18.Command("claim").alias("claims").description("File and track claims");
|
|
4258
|
+
claim.command("file").description("File a claim. The first time, accept the claims disclaimer in the portal").option("-d, --description <text>", "What happened (visible in shell history; prefer --description-file)").option("--description-file <file>", "Read what happened from a file, or - for stdin").option("--date <yyyy-mm-dd>", "When it happened").option("--policy <id>", "Policy id, if you know it").option("--json", "Output as JSON").action(async (options) => {
|
|
4259
|
+
let description = options.description;
|
|
4260
|
+
if (options.descriptionFile) {
|
|
4261
|
+
try {
|
|
4262
|
+
description = fs7.readFileSync(options.descriptionFile === "-" ? 0 : options.descriptionFile, "utf8").trim();
|
|
4263
|
+
} catch (e) {
|
|
4264
|
+
error(`Could not read ${options.descriptionFile}: ${e.message}`);
|
|
4265
|
+
process.exit(1);
|
|
4266
|
+
}
|
|
4267
|
+
}
|
|
4268
|
+
if (!description) {
|
|
4269
|
+
error("Describe what happened with --description-file <file> (or --description).");
|
|
4270
|
+
process.exit(1);
|
|
4271
|
+
}
|
|
4272
|
+
try {
|
|
4273
|
+
const r = await getClient().claims.file({ description, incidentDate: options.date, policyId: options.policy });
|
|
4274
|
+
if (options.json) {
|
|
4275
|
+
json(r);
|
|
4276
|
+
return;
|
|
4277
|
+
}
|
|
4278
|
+
success(r.message);
|
|
4279
|
+
keyValue({ Claim: r.claimId, Status: r.status, Portal: r.portalUrl });
|
|
4280
|
+
} catch (err) {
|
|
4281
|
+
if (err instanceof import_sdk8.DeleganceError && err.reason === "gate_not_acknowledged") {
|
|
4282
|
+
error("Accept the claims disclaimer once in the portal, then file again. It cannot be accepted for you.");
|
|
4283
|
+
info(String(err.body?.portalUrl ?? ""));
|
|
4284
|
+
process.exit(1);
|
|
4285
|
+
}
|
|
4286
|
+
failWith("Could not file the claim", err);
|
|
4287
|
+
}
|
|
4288
|
+
});
|
|
4289
|
+
claim.command("list").description("List claims").option("-s, --status <status>", "open, in_progress, or resolved").option("-l, --limit <n>", "Maximum results", "20").option("--json", "Output as JSON").action(async (options) => {
|
|
4290
|
+
try {
|
|
4291
|
+
const claims = await getClient().claims.list({ status: options.status, limit: Number(options.limit) || 20 });
|
|
4292
|
+
if (options.json) json(claims);
|
|
4293
|
+
else if (claims.length === 0) info("No claims.");
|
|
4294
|
+
else table(["Claim", "Status", "Incident", "Summary", "Last activity"], claims.map((c) => [c.claimId, c.status, c.incidentDate ?? "-", c.summary ?? "", formatDate(c.lastActivityAt)]));
|
|
4295
|
+
} catch (err) {
|
|
4296
|
+
failWith("Could not list claims", err);
|
|
4297
|
+
}
|
|
4298
|
+
});
|
|
4299
|
+
claim.command("status <claimId>").description("One claim").option("--json", "Output as JSON").action(async (claimId, options) => {
|
|
4300
|
+
try {
|
|
4301
|
+
const c = await getClient().claims.get(claimId);
|
|
4302
|
+
if (options.json) json(c);
|
|
4303
|
+
else keyValue({ Claim: c.claimId, Status: c.status, Incident: c.incidentDate ?? "-", Summary: c.summary ?? "", "Last activity": formatDate(c.lastActivityAt) });
|
|
4304
|
+
} catch (err) {
|
|
4305
|
+
failWith("Could not read the claim", err);
|
|
4306
|
+
}
|
|
4307
|
+
});
|
|
4308
|
+
return claim;
|
|
4309
|
+
}
|
|
4310
|
+
|
|
4311
|
+
// src/program.ts
|
|
4312
|
+
function createProgram() {
|
|
4313
|
+
const program = new import_commander19.Command();
|
|
4314
|
+
program.name("delegance").description("CLI for the Delegance Brokerage platform").version(CLI_VERSION).enablePositionalOptions().option("-v, --verbose", "Enable verbose output").option("--json", "Output in JSON format").hook("preAction", () => {
|
|
4315
|
+
migrateLegacyConfigKey();
|
|
4316
|
+
});
|
|
4317
|
+
program.addCommand(createSignupCommand());
|
|
4318
|
+
program.addCommand(createVerifyCommand());
|
|
4319
|
+
program.addCommand(createAuthCommand());
|
|
4320
|
+
program.addCommand(createCompanyCommand());
|
|
4321
|
+
program.addCommand(createDocumentCommand());
|
|
4322
|
+
program.addCommand(createChatCommand());
|
|
4323
|
+
program.addCommand(createQuoteCommand());
|
|
4324
|
+
program.addCommand(createPolicyCommand());
|
|
4325
|
+
program.addCommand(createCOICommand());
|
|
4326
|
+
program.addCommand(createSOVCommand());
|
|
4327
|
+
program.addCommand(createAppetiteCommand());
|
|
4328
|
+
program.addCommand(createLeadCommand());
|
|
4329
|
+
program.addCommand(createKnowledgeCommand());
|
|
4330
|
+
program.addCommand(createOnboardingCommand());
|
|
4331
|
+
program.addCommand(createUsageCommand());
|
|
4332
|
+
program.addCommand(createKeysCommand());
|
|
4333
|
+
program.addCommand(createBindCommand());
|
|
4334
|
+
program.addCommand(createPayCommand());
|
|
4335
|
+
program.addCommand(createBillingCommand());
|
|
4336
|
+
program.addCommand(createBondCommand());
|
|
4337
|
+
program.addCommand(createClaimCommand());
|
|
4338
|
+
program.addCommand(createFeedbackCommand());
|
|
4339
|
+
program.addCommand(createConfigCommand());
|
|
4340
|
+
return program;
|
|
4341
|
+
}
|
|
4342
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
4343
|
+
0 && (module.exports = {
|
|
4344
|
+
CLI_VERSION,
|
|
4345
|
+
clearConfig,
|
|
4346
|
+
clearCredentials,
|
|
4347
|
+
createAppetiteCommand,
|
|
4348
|
+
createAuthCommand,
|
|
4349
|
+
createCOICommand,
|
|
4350
|
+
createChatCommand,
|
|
4351
|
+
createCompanyCommand,
|
|
4352
|
+
createConfigCommand,
|
|
4353
|
+
createDocumentCommand,
|
|
4354
|
+
createKeysCommand,
|
|
4355
|
+
createKnowledgeCommand,
|
|
4356
|
+
createLeadCommand,
|
|
4357
|
+
createOnboardingCommand,
|
|
4358
|
+
createPolicyCommand,
|
|
4359
|
+
createProgram,
|
|
4360
|
+
createQuoteCommand,
|
|
4361
|
+
createSOVCommand,
|
|
4362
|
+
createUsageCommand,
|
|
4363
|
+
getAllConfig,
|
|
4364
|
+
getApiKey,
|
|
4365
|
+
getBaseUrl,
|
|
4366
|
+
getConfig,
|
|
4367
|
+
getConfigPath,
|
|
4368
|
+
isAuthenticated,
|
|
4369
|
+
maskApiKey,
|
|
4370
|
+
output,
|
|
4371
|
+
setConfig,
|
|
4372
|
+
storeApiKey
|
|
4373
|
+
});
|