lendctl-ai 0.2.0 β 0.2.1
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/README.md +32 -30
- package/dist/index.js +133 -34
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
# LendCtl AI
|
|
2
2
|
|
|
3
|
-
π¦ **Autonomous Lending Decision Agent** β
|
|
3
|
+
π¦ **Autonomous Lending Decision Agent** β Ask lending questions in plain English. Get instant, compliant decisions.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/lendctl-ai)
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
|
|
8
|
-
Ask lending questions in plain English. Get instant, compliant decisions with full audit trails.
|
|
9
|
-
|
|
10
8
|
```bash
|
|
11
|
-
lendctl-ai
|
|
9
|
+
npm install -g lendctl-ai
|
|
10
|
+
lendctl-ai ask 'Can I qualify for a $400k mortgage with $90k income and 720 credit?'
|
|
12
11
|
```
|
|
13
12
|
|
|
13
|
+
> β οΈ **Note:** Use single quotes around questions with `$` signs to prevent shell variable expansion.
|
|
14
|
+
|
|
14
15
|
## Features
|
|
15
16
|
|
|
16
17
|
- π€ **Natural Language Interface** β Ask lending questions like you'd ask a loan officer
|
|
17
|
-
- π **Autonomous Planning** β Decomposes complex questions into
|
|
18
|
+
- π **Autonomous Planning** β Decomposes complex questions into calculations
|
|
18
19
|
- β
**Self-Validation** β Verifies calculations and compliance automatically
|
|
19
|
-
- π **8
|
|
20
|
+
- π **8 Built-in Calculators** β Income, credit, mortgage, auto, personal, card, compliance, audit
|
|
20
21
|
- π **Audit Trail** β Every decision logged for regulatory compliance
|
|
21
22
|
- π **Multi-Channel** β CLI, REST API, WhatsApp, Telegram
|
|
22
23
|
|
|
@@ -27,10 +28,10 @@ lendctl-ai ask "Can I qualify for a $400k mortgage with $90k income and 720 cred
|
|
|
27
28
|
npm install -g lendctl-ai
|
|
28
29
|
|
|
29
30
|
# Set your OpenAI API key
|
|
30
|
-
export OPENAI_API_KEY=
|
|
31
|
+
export OPENAI_API_KEY=sk-...
|
|
31
32
|
|
|
32
|
-
# Ask a question
|
|
33
|
-
lendctl-ai ask
|
|
33
|
+
# Ask a question (use single quotes for $ signs!)
|
|
34
|
+
lendctl-ai ask 'I make $85,000/year with a 720 credit score. Can I afford a $350,000 house?'
|
|
34
35
|
|
|
35
36
|
# Interactive chat
|
|
36
37
|
lendctl-ai chat
|
|
@@ -41,10 +42,12 @@ lendctl-ai serve
|
|
|
41
42
|
|
|
42
43
|
## Example
|
|
43
44
|
|
|
45
|
+
```bash
|
|
46
|
+
$ lendctl-ai ask 'I make $85,000/year and want to buy a $350,000 home with 10% down. I have a 720 credit score and $400/month in car payments.'
|
|
44
47
|
```
|
|
45
|
-
$ lendctl-ai ask "I make $85,000/year and want to buy a $350,000 home with 10% down.
|
|
46
|
-
I have a 720 credit score and $400/month in car payments."
|
|
47
48
|
|
|
49
|
+
Output:
|
|
50
|
+
```
|
|
48
51
|
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
49
52
|
β LendCtl AI - Autonomous Lending Decision Agent β
|
|
50
53
|
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
@@ -81,20 +84,22 @@ Session: lendctl-abc123
|
|
|
81
84
|
Iterations: 1 | Time: 4823ms
|
|
82
85
|
```
|
|
83
86
|
|
|
84
|
-
##
|
|
87
|
+
## Built-in Calculators
|
|
88
|
+
|
|
89
|
+
All calculations run locally β no external CLI tools required.
|
|
85
90
|
|
|
86
|
-
|
|
|
87
|
-
|
|
91
|
+
| Calculator | Description |
|
|
92
|
+
|------------|-------------|
|
|
88
93
|
| `finctl` | Income analysis, DTI calculation |
|
|
89
|
-
| `creditctl` | Credit score analysis,
|
|
94
|
+
| `creditctl` | Credit score analysis, risk tier, rescore simulation |
|
|
90
95
|
| `mortctl` | Mortgage qualification, LTV, PMI, amortization |
|
|
91
|
-
| `autoloanctl` | Auto loan calculations, GAP insurance |
|
|
92
|
-
| `persctl` | Personal loan eligibility, debt consolidation |
|
|
96
|
+
| `autoloanctl` | Auto loan calculations, GAP insurance recommendations |
|
|
97
|
+
| `persctl` | Personal loan eligibility, debt consolidation analysis |
|
|
93
98
|
| `cardctl` | Credit limit estimation, balance transfer analysis |
|
|
94
|
-
| `compctl` | QM/ATR validation, TRID timing, adverse action |
|
|
95
|
-
| `auditctl` |
|
|
99
|
+
| `compctl` | QM/ATR validation, TRID timing, adverse action notices |
|
|
100
|
+
| `auditctl` | In-memory audit trail, compliance exports |
|
|
96
101
|
|
|
97
|
-
## API
|
|
102
|
+
## API Server
|
|
98
103
|
|
|
99
104
|
Start the server:
|
|
100
105
|
|
|
@@ -108,7 +113,7 @@ Query endpoint:
|
|
|
108
113
|
curl -X POST http://localhost:5055/api/v1/query \
|
|
109
114
|
-H "Content-Type: application/json" \
|
|
110
115
|
-d '{
|
|
111
|
-
"question": "Can I qualify for a
|
|
116
|
+
"question": "Can I qualify for a 400k mortgage with 90k income?",
|
|
112
117
|
"model": "gpt-4o",
|
|
113
118
|
"stream": false
|
|
114
119
|
}'
|
|
@@ -138,10 +143,10 @@ Response:
|
|
|
138
143
|
## Architecture
|
|
139
144
|
|
|
140
145
|
```
|
|
141
|
-
User Query β Planner (LLM) β Executor (
|
|
146
|
+
User Query β Planner (LLM) β Executor (Calculators) β Validator β Reporter
|
|
142
147
|
β
|
|
143
148
|
ββββββββββββββββββββββββ
|
|
144
|
-
β
|
|
149
|
+
β Built-in Tools β
|
|
145
150
|
β finctl Β· creditctl β
|
|
146
151
|
β mortctl Β· autoloanctlβ
|
|
147
152
|
β persctl Β· cardctl β
|
|
@@ -151,8 +156,6 @@ User Query β Planner (LLM) β Executor (Tools) β Validator β Reporter
|
|
|
151
156
|
|
|
152
157
|
## Configuration
|
|
153
158
|
|
|
154
|
-
Environment variables:
|
|
155
|
-
|
|
156
159
|
| Variable | Description | Default |
|
|
157
160
|
|----------|-------------|---------|
|
|
158
161
|
| `OPENAI_API_KEY` | OpenAI API key (required) | - |
|
|
@@ -172,11 +175,10 @@ lendctl-ai eval --sample 5
|
|
|
172
175
|
|
|
173
176
|
## Related
|
|
174
177
|
|
|
175
|
-
- [LendCtl
|
|
176
|
-
- [finctl](https://github.com/rsatyan/finctl) β
|
|
177
|
-
- [creditctl](https://github.com/rsatyan/creditctl) β
|
|
178
|
-
- [mortctl](https://github.com/rsatyan/mortctl) β
|
|
179
|
-
- [compctl](https://github.com/rsatyan/compctl) β Compliance checker
|
|
178
|
+
- [LendCtl Skill](https://github.com/rsatyan/lendctl-skill) β AI agent skill with 6 workflows
|
|
179
|
+
- [finctl](https://github.com/rsatyan/finctl) β Standalone income & DTI CLI
|
|
180
|
+
- [creditctl](https://github.com/rsatyan/creditctl) β Standalone credit analysis CLI
|
|
181
|
+
- [mortctl](https://github.com/rsatyan/mortctl) β Standalone mortgage underwriting CLI
|
|
180
182
|
|
|
181
183
|
## License
|
|
182
184
|
|
package/dist/index.js
CHANGED
|
@@ -25220,33 +25220,90 @@ ${source_default.cyan("β")} ${source_default.bold.white("LendCtl AI")} - Auto
|
|
|
25220
25220
|
${source_default.cyan("β")} ${source_default.gray("Powered by LendCtl CLI Suite")} ${source_default.cyan("β")}
|
|
25221
25221
|
${source_default.cyan("βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ")}
|
|
25222
25222
|
`;
|
|
25223
|
-
|
|
25224
|
-
|
|
25225
|
-
|
|
25223
|
+
function renderMarkdownToTerminal(md) {
|
|
25224
|
+
let result = md;
|
|
25225
|
+
result = result.replace(/^### (.+)$/gm, (_, h) => source_default.bold.underline(h));
|
|
25226
|
+
result = result.replace(/^## (.+)$/gm, (_, h) => source_default.bold.cyan(h));
|
|
25227
|
+
result = result.replace(/^# (.+)$/gm, (_, h) => source_default.bold.cyan.underline(h));
|
|
25228
|
+
result = result.replace(/\*\*([^*]+)\*\*/g, (_, t) => source_default.bold(t));
|
|
25229
|
+
result = result.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, (_, t) => source_default.italic(t));
|
|
25230
|
+
result = result.replace(/_([^_]+)_/g, (_, t) => source_default.italic(t));
|
|
25231
|
+
result = result.replace(/`([^`]+)`/g, (_, c) => source_default.yellow(c));
|
|
25232
|
+
result = result.replace(/^- (.+)$/gm, (_, item) => ` ${source_default.cyan("β’")} ${item}`);
|
|
25233
|
+
result = result.replace(/^\* (.+)$/gm, (_, item) => ` ${source_default.cyan("β’")} ${item}`);
|
|
25234
|
+
result = result.replace(/^(\d+)\. (.+)$/gm, (_, num, item) => ` ${source_default.cyan(num + ".")} ${item}`);
|
|
25235
|
+
result = result.replace(/β/g, source_default.green("β"));
|
|
25236
|
+
result = result.replace(/β/g, source_default.red("β"));
|
|
25237
|
+
result = result.replace(/β οΈ/g, source_default.yellow("β "));
|
|
25238
|
+
result = result.replace(/^---+$/gm, source_default.gray("β".repeat(50)));
|
|
25239
|
+
result = result.replace(/^===+$/gm, source_default.gray("β".repeat(50)));
|
|
25240
|
+
return result;
|
|
25241
|
+
}
|
|
25242
|
+
function formatAsJson(result) {
|
|
25243
|
+
return JSON.stringify({
|
|
25244
|
+
sessionId: result.sessionId,
|
|
25245
|
+
success: result.success,
|
|
25246
|
+
iterations: result.iterations,
|
|
25247
|
+
totalDurationMs: result.totalDurationMs,
|
|
25248
|
+
plan: result.plan,
|
|
25249
|
+
results: result.results,
|
|
25250
|
+
validation: result.validation,
|
|
25251
|
+
report: typeof result.report === "string" ? result.report : "[streaming]"
|
|
25252
|
+
}, null, 2);
|
|
25253
|
+
}
|
|
25254
|
+
program2.name("lendctl-ai").description("Autonomous lending decision agent powered by LendCtl CLI suite").version("0.2.1");
|
|
25255
|
+
program2.command("ask").description("Ask a lending question").argument("<question>", "Your lending question").option("-m, --model <model>", "LLM model to use", "gpt-4o").option("-i, --iterations <n>", "Max planning iterations", "3").option("-v, --verbose", "Show detailed output").option("-j, --json", "Output as JSON").option("-f, --format <format>", "Output format: plain, json, markdown", "plain").option("--quick", "Quick mode (skip LLM report generation)").action(async (question, options) => {
|
|
25256
|
+
const outputJson = options.json || options.format === "json";
|
|
25257
|
+
const outputMarkdown = options.format === "markdown";
|
|
25258
|
+
if (!outputJson) {
|
|
25259
|
+
console.log(banner);
|
|
25260
|
+
}
|
|
25226
25261
|
const agent = new LendCtlAgent({
|
|
25227
25262
|
model: options.model,
|
|
25228
25263
|
maxIterations: parseInt(options.iterations),
|
|
25229
25264
|
verbose: options.verbose
|
|
25230
25265
|
});
|
|
25231
|
-
|
|
25266
|
+
if (!outputJson) {
|
|
25267
|
+
console.log(source_default.blue(`\uD83D\uDCCB Question: ${question}
|
|
25232
25268
|
`));
|
|
25269
|
+
}
|
|
25233
25270
|
try {
|
|
25234
25271
|
if (options.quick) {
|
|
25235
|
-
|
|
25272
|
+
if (!outputJson) {
|
|
25273
|
+
console.log(source_default.gray(`Running in quick mode...
|
|
25236
25274
|
`));
|
|
25275
|
+
}
|
|
25237
25276
|
const result = await agent.quickQuery(question);
|
|
25238
|
-
|
|
25239
|
-
|
|
25277
|
+
if (outputJson) {
|
|
25278
|
+
console.log(JSON.stringify({
|
|
25279
|
+
sessionId: result.sessionId,
|
|
25280
|
+
summary: result.summary,
|
|
25281
|
+
results: result.results,
|
|
25282
|
+
validation: result.validation
|
|
25283
|
+
}, null, 2));
|
|
25284
|
+
} else {
|
|
25285
|
+
console.log(outputMarkdown ? result.summary : renderMarkdownToTerminal(result.summary));
|
|
25286
|
+
console.log(source_default.gray(`
|
|
25240
25287
|
Session: ${result.sessionId}`));
|
|
25288
|
+
}
|
|
25241
25289
|
} else {
|
|
25242
|
-
|
|
25243
|
-
|
|
25244
|
-
|
|
25245
|
-
|
|
25246
|
-
|
|
25290
|
+
let spinnerInterval;
|
|
25291
|
+
if (!outputJson) {
|
|
25292
|
+
const spinner = ["β ", "β ", "β Ή", "β Έ", "β Ό", "β ΄", "β ¦", "β §", "β ", "β "];
|
|
25293
|
+
let i = 0;
|
|
25294
|
+
spinnerInterval = setInterval(() => {
|
|
25295
|
+
process.stdout.write(`\r${source_default.cyan(spinner[i++ % spinner.length])} Analyzing...`);
|
|
25296
|
+
}, 100);
|
|
25297
|
+
}
|
|
25247
25298
|
const result = await agent.query(question);
|
|
25248
|
-
|
|
25249
|
-
|
|
25299
|
+
if (spinnerInterval) {
|
|
25300
|
+
clearInterval(spinnerInterval);
|
|
25301
|
+
process.stdout.write("\r" + " ".repeat(20) + "\r");
|
|
25302
|
+
}
|
|
25303
|
+
if (outputJson) {
|
|
25304
|
+
console.log(formatAsJson(result));
|
|
25305
|
+
return;
|
|
25306
|
+
}
|
|
25250
25307
|
if (options.verbose) {
|
|
25251
25308
|
console.log(source_default.yellow(`
|
|
25252
25309
|
\uD83D\uDCCB Plan:`));
|
|
@@ -25272,12 +25329,13 @@ Session: ${result.sessionId}`));
|
|
|
25272
25329
|
\uD83D\uDCDD Report:
|
|
25273
25330
|
`));
|
|
25274
25331
|
if (typeof result.report === "string") {
|
|
25275
|
-
console.log(result.report);
|
|
25332
|
+
console.log(outputMarkdown ? result.report : renderMarkdownToTerminal(result.report));
|
|
25276
25333
|
} else {
|
|
25334
|
+
let fullReport = "";
|
|
25277
25335
|
for await (const chunk of result.report) {
|
|
25278
|
-
|
|
25336
|
+
fullReport += chunk;
|
|
25279
25337
|
}
|
|
25280
|
-
console.log();
|
|
25338
|
+
console.log(outputMarkdown ? fullReport : renderMarkdownToTerminal(fullReport));
|
|
25281
25339
|
}
|
|
25282
25340
|
console.log(source_default.gray(`
|
|
25283
25341
|
βββββββββββββββββββββββββββββββββββββββββ`));
|
|
@@ -25285,50 +25343,91 @@ Session: ${result.sessionId}`));
|
|
|
25285
25343
|
console.log(source_default.gray(`Iterations: ${result.iterations} | Time: ${result.totalDurationMs}ms`));
|
|
25286
25344
|
}
|
|
25287
25345
|
} catch (error) {
|
|
25288
|
-
|
|
25346
|
+
if (outputJson) {
|
|
25347
|
+
console.log(JSON.stringify({ error: error.message }, null, 2));
|
|
25348
|
+
} else {
|
|
25349
|
+
console.error(source_default.red(`
|
|
25289
25350
|
β Error: ${error.message}`));
|
|
25290
|
-
|
|
25291
|
-
|
|
25351
|
+
if (options.verbose) {
|
|
25352
|
+
console.error(error.stack);
|
|
25353
|
+
}
|
|
25292
25354
|
}
|
|
25293
25355
|
process.exit(1);
|
|
25294
25356
|
}
|
|
25295
25357
|
});
|
|
25296
|
-
program2.command("chat").description("Interactive chat mode").option("-m, --model <model>", "LLM model to use", "gpt-4o").action(async (options) => {
|
|
25297
|
-
|
|
25298
|
-
|
|
25358
|
+
program2.command("chat").description("Interactive chat mode").option("-m, --model <model>", "LLM model to use", "gpt-4o").option("-j, --json", "Output responses as JSON").action(async (options) => {
|
|
25359
|
+
const outputJson = options.json;
|
|
25360
|
+
if (!outputJson) {
|
|
25361
|
+
console.log(banner);
|
|
25362
|
+
console.log(source_default.gray(`Type your lending questions. Press Ctrl+C or type "quit" to exit.
|
|
25299
25363
|
`));
|
|
25364
|
+
}
|
|
25300
25365
|
const agent = new LendCtlAgent({
|
|
25301
25366
|
model: options.model,
|
|
25302
25367
|
stream: true
|
|
25303
25368
|
});
|
|
25369
|
+
process.on("SIGINT", () => {
|
|
25370
|
+
if (!outputJson) {
|
|
25371
|
+
console.log(source_default.gray(`
|
|
25372
|
+
|
|
25373
|
+
Goodbye! \uD83D\uDC4B`));
|
|
25374
|
+
}
|
|
25375
|
+
process.exit(0);
|
|
25376
|
+
});
|
|
25304
25377
|
while (true) {
|
|
25305
|
-
|
|
25378
|
+
let question;
|
|
25379
|
+
try {
|
|
25380
|
+
question = await esm_default2({ message: outputJson ? "> " : source_default.blue("You:") });
|
|
25381
|
+
} catch (error) {
|
|
25382
|
+
if (error.name === "ExitPromptError" || error.message?.includes("SIGINT") || error.message?.includes("force closed")) {
|
|
25383
|
+
if (!outputJson) {
|
|
25384
|
+
console.log(source_default.gray(`
|
|
25385
|
+
|
|
25386
|
+
Goodbye! \uD83D\uDC4B`));
|
|
25387
|
+
}
|
|
25388
|
+
process.exit(0);
|
|
25389
|
+
}
|
|
25390
|
+
throw error;
|
|
25391
|
+
}
|
|
25306
25392
|
if (question.toLowerCase() === "quit" || question.toLowerCase() === "exit") {
|
|
25307
|
-
|
|
25393
|
+
if (!outputJson) {
|
|
25394
|
+
console.log(source_default.gray(`
|
|
25308
25395
|
Goodbye! \uD83D\uDC4B`));
|
|
25396
|
+
}
|
|
25309
25397
|
break;
|
|
25310
25398
|
}
|
|
25311
25399
|
if (!question.trim()) {
|
|
25312
25400
|
continue;
|
|
25313
25401
|
}
|
|
25314
|
-
|
|
25402
|
+
if (!outputJson) {
|
|
25403
|
+
console.log(source_default.green(`
|
|
25315
25404
|
Agent:`));
|
|
25405
|
+
}
|
|
25316
25406
|
try {
|
|
25317
25407
|
const result = await agent.query(question);
|
|
25318
|
-
if (
|
|
25319
|
-
console.log(result
|
|
25408
|
+
if (outputJson) {
|
|
25409
|
+
console.log(formatAsJson(result));
|
|
25320
25410
|
} else {
|
|
25321
|
-
|
|
25322
|
-
|
|
25411
|
+
let reportText = "";
|
|
25412
|
+
if (typeof result.report === "string") {
|
|
25413
|
+
reportText = result.report;
|
|
25414
|
+
} else {
|
|
25415
|
+
for await (const chunk of result.report) {
|
|
25416
|
+
reportText += chunk;
|
|
25417
|
+
}
|
|
25323
25418
|
}
|
|
25324
|
-
console.log();
|
|
25325
|
-
|
|
25326
|
-
console.log(source_default.gray(`
|
|
25419
|
+
console.log(renderMarkdownToTerminal(reportText));
|
|
25420
|
+
console.log(source_default.gray(`
|
|
25327
25421
|
[${result.iterations} iterations, ${result.totalDurationMs}ms]
|
|
25328
25422
|
`));
|
|
25423
|
+
}
|
|
25329
25424
|
} catch (error) {
|
|
25330
|
-
|
|
25425
|
+
if (outputJson) {
|
|
25426
|
+
console.log(JSON.stringify({ error: error.message }, null, 2));
|
|
25427
|
+
} else {
|
|
25428
|
+
console.error(source_default.red(`Error: ${error.message}
|
|
25331
25429
|
`));
|
|
25430
|
+
}
|
|
25332
25431
|
}
|
|
25333
25432
|
}
|
|
25334
25433
|
});
|