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.
Files changed (3) hide show
  1. package/README.md +32 -30
  2. package/dist/index.js +133 -34
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,22 +1,23 @@
1
1
  # LendCtl AI
2
2
 
3
- 🏦 **Autonomous Lending Decision Agent** β€” powered by the [LendCtl CLI Suite](https://github.com/rsatyan/lendctl-skill)
3
+ 🏦 **Autonomous Lending Decision Agent** β€” Ask lending questions in plain English. Get instant, compliant decisions.
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/lendctl-ai.svg)](https://www.npmjs.com/package/lendctl-ai)
6
6
  [![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](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 ask "Can I qualify for a $400k mortgage with $90k income and 720 credit?"
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 tool calls
18
+ - πŸ“‹ **Autonomous Planning** β€” Decomposes complex questions into calculations
18
19
  - βœ… **Self-Validation** β€” Verifies calculations and compliance automatically
19
- - πŸ“Š **8 Integrated Tools** β€” Income, credit, mortgage, auto, personal, card, compliance, audit
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=your-key
31
+ export OPENAI_API_KEY=sk-...
31
32
 
32
- # Ask a question
33
- lendctl-ai ask "I make $85,000/year with a 720 credit score. Can I afford a $350,000 house?"
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
- ## Integrated Tools
87
+ ## Built-in Calculators
88
+
89
+ All calculations run locally β€” no external CLI tools required.
85
90
 
86
- | Tool | Description |
87
- |------|-------------|
91
+ | Calculator | Description |
92
+ |------------|-------------|
88
93
  | `finctl` | Income analysis, DTI calculation |
89
- | `creditctl` | Credit score analysis, rapid rescore simulation |
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` | Immutable audit trail, compliance exports |
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 $400k mortgage with $90k income?",
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 (Tools) β†’ Validator β†’ Reporter
146
+ User Query β†’ Planner (LLM) β†’ Executor (Calculators) β†’ Validator β†’ Reporter
142
147
  ↓
143
148
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
144
- β”‚ LendCtl CLI Suite β”‚
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 Suite](https://github.com/rsatyan/lendctl-skill) β€” The 8 CLI tools powering this agent
176
- - [finctl](https://github.com/rsatyan/finctl) β€” Income & DTI calculator
177
- - [creditctl](https://github.com/rsatyan/creditctl) β€” Credit report analyzer
178
- - [mortctl](https://github.com/rsatyan/mortctl) β€” Mortgage underwriting
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
- program2.name("lendctl-ai").description("Autonomous lending decision agent powered by LendCtl CLI suite").version("0.2.0");
25224
- 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("--quick", "Quick mode (skip LLM report generation)").action(async (question, options) => {
25225
- console.log(banner);
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
- console.log(source_default.blue(`\uD83D\uDCCB Question: ${question}
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
- console.log(source_default.gray(`Running in quick mode...
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
- console.log(result.summary);
25239
- console.log(source_default.gray(`
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
- const spinner = ["β ‹", "β ™", "β Ή", "β Έ", "β Ό", "β ΄", "β ¦", "β §", "β ‡", "⠏"];
25243
- let i = 0;
25244
- const spinnerInterval = setInterval(() => {
25245
- process.stdout.write(`\r${source_default.cyan(spinner[i++ % spinner.length])} Analyzing...`);
25246
- }, 100);
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
- clearInterval(spinnerInterval);
25249
- process.stdout.write("\r" + " ".repeat(20) + "\r");
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
- process.stdout.write(chunk);
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
- console.error(source_default.red(`
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
- if (options.verbose) {
25291
- console.error(error.stack);
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
- console.log(banner);
25298
- console.log(source_default.gray(`Type your lending questions. Enter "quit" to exit.
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
- const question = await esm_default2({ message: source_default.blue("You:") });
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
- console.log(source_default.gray(`
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
- console.log(source_default.green(`
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 (typeof result.report === "string") {
25319
- console.log(result.report);
25408
+ if (outputJson) {
25409
+ console.log(formatAsJson(result));
25320
25410
  } else {
25321
- for await (const chunk of result.report) {
25322
- process.stdout.write(chunk);
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
- console.error(source_default.red(`Error: ${error.message}
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
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lendctl-ai",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Autonomous lending decision agent powered by LendCtl CLI suite",
5
5
  "author": "Satyan Avatara <rsatyan@gmail.com>",
6
6
  "license": "Apache-2.0",