@fettstorch/clai 0.1.15 → 0.1.17
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/.claude/settings.local.json +3 -1
- package/README.md +25 -9
- package/dist/cli.js +33 -14
- package/dist/index.js +10 -2
- package/package.json +1 -1
- package/src/cli.ts +213 -186
- package/src/index.ts +57 -44
- package/src/openai.ts +73 -73
- package/src/scraper.ts +308 -310
- package/src/summarizer.ts +73 -73
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Command Line AI Interface (CLAI)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
An AI-powered question answering tool for the terminal. Get instant answers to your questions using OpenAI, with optional web crawling capabilities.
|
|
4
4
|
Built for fun in order to learn more about AI and CLIs.
|
|
5
5
|
|
|
6
6
|
## Installation
|
|
@@ -26,29 +26,45 @@ Using both the CLI tool and the clai function requires an OpenAI API key. For th
|
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
## Features
|
|
29
|
-
|
|
29
|
+
|
|
30
|
+
### Default Behavior - Direct AI Answering
|
|
31
|
+
By default, CLAI uses OpenAI directly to answer your questions without web crawling:
|
|
32
|
+
|
|
30
33
|
```bash
|
|
31
|
-
clai https://example.com
|
|
32
34
|
clai "how tall can giraffes get?"
|
|
33
35
|
clai how tall can giraffes get
|
|
36
|
+
clai "explain quantum computing"
|
|
34
37
|
clai
|
|
35
38
|
```
|
|
39
|
+
|
|
40
|
+
### Optional Web Crawling
|
|
41
|
+
Use the `-c` or `--crawl` flag to enable web scraping and search engine functionality:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
clai -c "latest news about AI"
|
|
45
|
+
clai --crawl https://example.com
|
|
46
|
+
clai -c "current Bitcoin price"
|
|
47
|
+
```
|
|
48
|
+
|
|
36
49
|
<img width="400" src="https://github.com/user-attachments/assets/002b3e05-5c77-4f4d-8aa3-ecb7412e9538" />
|
|
37
50
|
|
|
38
|
-
The
|
|
39
|
-
When passing a search query without quotes make sure not to use any special characters, that might confuse the CLI e.g. ?
|
|
51
|
+
The CLI accepts questions, search queries, or URLs. When passing queries without quotes, avoid special characters that might confuse the CLI (e.g., `?`).
|
|
40
52
|
|
|
41
53
|
### Programmatic API
|
|
42
54
|
```ts
|
|
43
55
|
import clai from 'clai';
|
|
44
56
|
|
|
45
|
-
|
|
57
|
+
// Direct AI answering (default)
|
|
58
|
+
const { summary, links, sources } = await clai('how tall can giraffes get?', 'your-openai-api-key');
|
|
59
|
+
|
|
60
|
+
// With web crawling enabled
|
|
61
|
+
const { summary, links, sources } = await clai('https://example.com', 'your-openai-api-key', true);
|
|
46
62
|
```
|
|
47
63
|
|
|
48
64
|
## Issues
|
|
49
|
-
-
|
|
50
|
-
- large sites might surpass the token limit (currently not handled gracefully)
|
|
51
|
-
-
|
|
65
|
+
- When using web crawling (`-c` flag): search engines often fail due to scraping protections
|
|
66
|
+
- When using web crawling (`-c` flag): large sites might surpass the token limit (currently not handled gracefully)
|
|
67
|
+
- Web crawling results may not always fit the user's query well
|
|
52
68
|
|
|
53
69
|
## License
|
|
54
70
|
|
package/dist/cli.js
CHANGED
|
@@ -54305,7 +54305,7 @@ function ora(options) {
|
|
|
54305
54305
|
return new Ora(options);
|
|
54306
54306
|
}
|
|
54307
54307
|
// package.json
|
|
54308
|
-
var version = "0.1.
|
|
54308
|
+
var version = "0.1.17";
|
|
54309
54309
|
var package_default = {
|
|
54310
54310
|
name: "@fettstorch/clai",
|
|
54311
54311
|
version,
|
|
@@ -73344,7 +73344,15 @@ Provide a thorough, educational response that directly addresses the user's quer
|
|
|
73344
73344
|
}
|
|
73345
73345
|
|
|
73346
73346
|
// src/index.ts
|
|
73347
|
-
async function clai(input, openAIKey) {
|
|
73347
|
+
async function clai(input, openAIKey, useCrawling = false) {
|
|
73348
|
+
if (!useCrawling) {
|
|
73349
|
+
const result2 = await summarizeQuery(input, openAIKey);
|
|
73350
|
+
return {
|
|
73351
|
+
summary: result2.textual.trim(),
|
|
73352
|
+
links: result2.links,
|
|
73353
|
+
sources: ["OpenAI Knowledge Base"]
|
|
73354
|
+
};
|
|
73355
|
+
}
|
|
73348
73356
|
const scrapedData = await scrape(input);
|
|
73349
73357
|
const usefulData = scrapedData.filter((data2) => data2.content.length > 200 && !data2.content.includes("Wikipedia does not have an article") && !data2.content.includes("page not found") && !data2.content.includes("404") && !data2.content.includes("error"));
|
|
73350
73358
|
if (usefulData.length > 0) {
|
|
@@ -73359,7 +73367,7 @@ ${data2.content}`).join(`
|
|
|
73359
73367
|
sources: usefulData.map((data2) => data2.url)
|
|
73360
73368
|
};
|
|
73361
73369
|
}
|
|
73362
|
-
console.log("No scraped data available -
|
|
73370
|
+
console.log("No scraped data available - falling back to OpenAI...");
|
|
73363
73371
|
const result = await summarizeQuery(input, openAIKey);
|
|
73364
73372
|
return {
|
|
73365
73373
|
summary: result.textual.trim(),
|
|
@@ -73369,11 +73377,18 @@ ${data2.content}`).join(`
|
|
|
73369
73377
|
}
|
|
73370
73378
|
|
|
73371
73379
|
// src/cli.ts
|
|
73380
|
+
try {
|
|
73381
|
+
const figures2 = (()=>{throw new Error("Cannot require module "+"figures");})();
|
|
73382
|
+
if (figures2) {
|
|
73383
|
+
figures2.pointer = String.fromCodePoint(62);
|
|
73384
|
+
figures2.pointerSmall = String.fromCodePoint(62);
|
|
73385
|
+
}
|
|
73386
|
+
} catch (e2) {}
|
|
73372
73387
|
var program2 = new Command;
|
|
73373
73388
|
async function main2() {
|
|
73374
73389
|
console.log(`[clAi]::${source_default.cyan(version)}`);
|
|
73375
73390
|
try {
|
|
73376
|
-
program2.name("clai").description("AI-powered
|
|
73391
|
+
program2.name("clai").description("AI-powered query answering tool").version(package_default.version).argument("[input...]", "Question or search terms to analyze").option("-c, --crawl", "Enable web crawling and search engine scraping (fallback to direct AI otherwise)").action(async (inputs, options) => {
|
|
73377
73392
|
const openAIKey = process.env.OPENAI_API_KEY;
|
|
73378
73393
|
if (!openAIKey) {
|
|
73379
73394
|
console.error(source_default.red(`${String.fromCodePoint(10060)} OPENAI_API_KEY environment variable is not set`));
|
|
@@ -73385,7 +73400,7 @@ async function main2() {
|
|
|
73385
73400
|
{
|
|
73386
73401
|
type: "input",
|
|
73387
73402
|
name: "input",
|
|
73388
|
-
message: "Enter a
|
|
73403
|
+
message: "Enter a question or search query:",
|
|
73389
73404
|
validate: (input2) => input2.length > 0,
|
|
73390
73405
|
prefix: String.fromCodePoint(63),
|
|
73391
73406
|
theme: {
|
|
@@ -73395,7 +73410,7 @@ async function main2() {
|
|
|
73395
73410
|
]);
|
|
73396
73411
|
input = answers.input;
|
|
73397
73412
|
}
|
|
73398
|
-
await analyzeInput(input, openAIKey);
|
|
73413
|
+
await analyzeInput(input, openAIKey, options.crawl);
|
|
73399
73414
|
process.exit(0);
|
|
73400
73415
|
});
|
|
73401
73416
|
await program2.parseAsync();
|
|
@@ -73444,9 +73459,9 @@ ${source_default.yellow.bold("\u2550\u2550\u2550 ")}${source_default.yellow.bold
|
|
|
73444
73459
|
const boldHandled = headingHandled.replace(/\*\*(.*?)\*\*/g, (_3, content) => source_default.bold(content));
|
|
73445
73460
|
return boldHandled;
|
|
73446
73461
|
}
|
|
73447
|
-
async function analyzeInput(input, openAIKey) {
|
|
73462
|
+
async function analyzeInput(input, openAIKey, useCrawling = false) {
|
|
73448
73463
|
const spinner = ora({
|
|
73449
|
-
text: "Thinking...",
|
|
73464
|
+
text: useCrawling ? "Searching and analyzing..." : "Thinking...",
|
|
73450
73465
|
spinner: {
|
|
73451
73466
|
frames: [
|
|
73452
73467
|
String.fromCodePoint(10265),
|
|
@@ -73462,7 +73477,7 @@ async function analyzeInput(input, openAIKey) {
|
|
|
73462
73477
|
}
|
|
73463
73478
|
}).start();
|
|
73464
73479
|
try {
|
|
73465
|
-
const result = await clai(input, openAIKey);
|
|
73480
|
+
const result = await clai(input, openAIKey, useCrawling);
|
|
73466
73481
|
spinner.stop();
|
|
73467
73482
|
console.log(`${String.fromCodePoint(9989)} AHA!`);
|
|
73468
73483
|
console.log(source_default.green.bold(`
|
|
@@ -73477,7 +73492,10 @@ ${String.fromCodePoint(128221)} \u2550\u2550\u2550 Summary \u2550\u2550\u2550 :`
|
|
|
73477
73492
|
|
|
73478
73493
|
What now?:`,
|
|
73479
73494
|
choices: [
|
|
73480
|
-
{
|
|
73495
|
+
{
|
|
73496
|
+
name: source_default.yellow(`${String.fromCodePoint(128269)} New search`),
|
|
73497
|
+
value: "new"
|
|
73498
|
+
},
|
|
73481
73499
|
...result.links.map((link) => ({
|
|
73482
73500
|
name: `${source_default.bold(link.name)}: ${source_default.cyan(link.url)}`,
|
|
73483
73501
|
value: link.url
|
|
@@ -73486,7 +73504,8 @@ What now?:`,
|
|
|
73486
73504
|
],
|
|
73487
73505
|
prefix: String.fromCodePoint(63),
|
|
73488
73506
|
theme: {
|
|
73489
|
-
prefix: String.fromCodePoint(63)
|
|
73507
|
+
prefix: String.fromCodePoint(63),
|
|
73508
|
+
pointer: String.fromCodePoint(62)
|
|
73490
73509
|
}
|
|
73491
73510
|
}
|
|
73492
73511
|
]);
|
|
@@ -73495,7 +73514,7 @@ What now?:`,
|
|
|
73495
73514
|
{
|
|
73496
73515
|
type: "input",
|
|
73497
73516
|
name: "input",
|
|
73498
|
-
message: "Enter a
|
|
73517
|
+
message: "Enter a question or search query:",
|
|
73499
73518
|
validate: (input2) => input2.length > 0,
|
|
73500
73519
|
prefix: String.fromCodePoint(63),
|
|
73501
73520
|
theme: {
|
|
@@ -73503,9 +73522,9 @@ What now?:`,
|
|
|
73503
73522
|
}
|
|
73504
73523
|
}
|
|
73505
73524
|
]);
|
|
73506
|
-
await analyzeInput(newInput, openAIKey);
|
|
73525
|
+
await analyzeInput(newInput, openAIKey, useCrawling);
|
|
73507
73526
|
} else if (selectedLink && selectedLink !== "exit") {
|
|
73508
|
-
await analyzeInput(selectedLink, openAIKey);
|
|
73527
|
+
await analyzeInput(selectedLink, openAIKey, useCrawling);
|
|
73509
73528
|
}
|
|
73510
73529
|
} catch (error) {
|
|
73511
73530
|
spinner?.stop();
|
package/dist/index.js
CHANGED
|
@@ -46331,7 +46331,15 @@ Provide a thorough, educational response that directly addresses the user's quer
|
|
|
46331
46331
|
}
|
|
46332
46332
|
|
|
46333
46333
|
// src/index.ts
|
|
46334
|
-
async function clai(input, openAIKey) {
|
|
46334
|
+
async function clai(input, openAIKey, useCrawling = false) {
|
|
46335
|
+
if (!useCrawling) {
|
|
46336
|
+
const result2 = await summarizeQuery(input, openAIKey);
|
|
46337
|
+
return {
|
|
46338
|
+
summary: result2.textual.trim(),
|
|
46339
|
+
links: result2.links,
|
|
46340
|
+
sources: ["OpenAI Knowledge Base"]
|
|
46341
|
+
};
|
|
46342
|
+
}
|
|
46335
46343
|
const scrapedData = await scrape(input);
|
|
46336
46344
|
const usefulData = scrapedData.filter((data2) => data2.content.length > 200 && !data2.content.includes("Wikipedia does not have an article") && !data2.content.includes("page not found") && !data2.content.includes("404") && !data2.content.includes("error"));
|
|
46337
46345
|
if (usefulData.length > 0) {
|
|
@@ -46346,7 +46354,7 @@ ${data2.content}`).join(`
|
|
|
46346
46354
|
sources: usefulData.map((data2) => data2.url)
|
|
46347
46355
|
};
|
|
46348
46356
|
}
|
|
46349
|
-
console.log("No scraped data available -
|
|
46357
|
+
console.log("No scraped data available - falling back to OpenAI...");
|
|
46350
46358
|
const result = await summarizeQuery(input, openAIKey);
|
|
46351
46359
|
return {
|
|
46352
46360
|
summary: result.textual.trim(),
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -1,200 +1,227 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
import { when } from
|
|
3
|
-
import chalk from
|
|
4
|
-
import { Command } from
|
|
5
|
-
import inquirer from
|
|
6
|
-
import ora from
|
|
7
|
-
import pkg from
|
|
8
|
-
import { clai } from
|
|
9
|
-
import { version } from
|
|
2
|
+
import { when } from '@fettstorch/jule'
|
|
3
|
+
import chalk from 'chalk'
|
|
4
|
+
import { Command } from 'commander'
|
|
5
|
+
import inquirer from 'inquirer'
|
|
6
|
+
import ora from 'ora'
|
|
7
|
+
import pkg from '../package.json' assert { type: 'json' }
|
|
8
|
+
import { clai } from './index'
|
|
9
|
+
import { version } from '../package.json'
|
|
10
|
+
|
|
11
|
+
// Override inquirer's figures to prevent Unicode corruption
|
|
12
|
+
try {
|
|
13
|
+
const figures = require('figures')
|
|
14
|
+
if (figures) {
|
|
15
|
+
figures.pointer = String.fromCodePoint(0x003e) // >
|
|
16
|
+
figures.pointerSmall = String.fromCodePoint(0x003e) // >
|
|
17
|
+
}
|
|
18
|
+
} catch (e) {
|
|
19
|
+
// figures module not available, continue
|
|
20
|
+
}
|
|
10
21
|
|
|
11
|
-
const program = new Command()
|
|
22
|
+
const program = new Command()
|
|
12
23
|
|
|
13
24
|
async function main() {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
25
|
+
console.log(`[clAi]::${chalk.cyan(version)}`)
|
|
26
|
+
try {
|
|
27
|
+
program
|
|
28
|
+
.name('clai')
|
|
29
|
+
.description('AI-powered query answering tool')
|
|
30
|
+
.version(pkg.version)
|
|
31
|
+
.argument('[input...]', 'Question or search terms to analyze')
|
|
32
|
+
.option(
|
|
33
|
+
'-c, --crawl',
|
|
34
|
+
'Enable web crawling and search engine scraping (fallback to direct AI otherwise)',
|
|
35
|
+
)
|
|
36
|
+
.action(async (inputs: string[], options) => {
|
|
37
|
+
const openAIKey = process.env.OPENAI_API_KEY
|
|
38
|
+
|
|
39
|
+
if (!openAIKey) {
|
|
40
|
+
console.error(
|
|
41
|
+
chalk.red(
|
|
42
|
+
`${String.fromCodePoint(0x274c)} OPENAI_API_KEY environment variable is not set`,
|
|
43
|
+
),
|
|
44
|
+
)
|
|
45
|
+
process.exit(1)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let input = inputs?.join(' ')
|
|
49
|
+
|
|
50
|
+
if (!input) {
|
|
51
|
+
const answers = await inquirer.prompt([
|
|
52
|
+
{
|
|
53
|
+
type: 'input',
|
|
54
|
+
name: 'input',
|
|
55
|
+
message: 'Enter a question or search query:',
|
|
56
|
+
validate: (input) => input.length > 0,
|
|
57
|
+
prefix: String.fromCodePoint(0x003f), // ?
|
|
58
|
+
theme: {
|
|
59
|
+
prefix: String.fromCodePoint(0x003f), // ?
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
])
|
|
63
|
+
input = answers.input
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
await analyzeInput(input, openAIKey, options.crawl)
|
|
67
|
+
process.exit(0)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
await program.parseAsync()
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error(chalk.red('Fatal error:'), error)
|
|
73
|
+
process.exit(1)
|
|
74
|
+
}
|
|
58
75
|
}
|
|
59
76
|
|
|
60
77
|
async function animateText(text: string, delay = 25) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
78
|
+
let shouldComplete = false
|
|
79
|
+
|
|
80
|
+
// Setup keypress listener
|
|
81
|
+
const keypressHandler = (str: string, key: { name: string }) => {
|
|
82
|
+
if (key.name === 'return') {
|
|
83
|
+
shouldComplete = true
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
process.stdin.on('keypress', keypressHandler)
|
|
88
|
+
|
|
89
|
+
// Enable raw mode to get keypress events
|
|
90
|
+
process.stdin.setRawMode(true)
|
|
91
|
+
process.stdin.resume()
|
|
92
|
+
|
|
93
|
+
// Small delay to ensure stdin is ready for keypress capture
|
|
94
|
+
// This fixes the issue where the first animation can't be interrupted
|
|
95
|
+
await new Promise((resolve) => setTimeout(resolve, 50))
|
|
96
|
+
|
|
97
|
+
let currentIndex = 0
|
|
98
|
+
while (currentIndex < text.length) {
|
|
99
|
+
if (shouldComplete) {
|
|
100
|
+
// Show remaining text immediately
|
|
101
|
+
process.stdout.write(text.slice(currentIndex))
|
|
102
|
+
break
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
process.stdout.write(text[currentIndex])
|
|
106
|
+
currentIndex++
|
|
107
|
+
|
|
108
|
+
if (!shouldComplete) {
|
|
109
|
+
await new Promise((resolve) => setTimeout(resolve, delay))
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Cleanup
|
|
114
|
+
process.stdin.setRawMode(false)
|
|
115
|
+
process.stdin.pause()
|
|
116
|
+
process.stdin.removeListener('keypress', keypressHandler)
|
|
117
|
+
|
|
118
|
+
process.stdout.write('\n')
|
|
102
119
|
}
|
|
103
120
|
|
|
104
121
|
function formatMarkdownForTerminal(text: string): string {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
122
|
+
// Handle headings first
|
|
123
|
+
const headingHandled = text.replace(
|
|
124
|
+
/^(#{1,3})\s+(.*?)$/gm,
|
|
125
|
+
(_, hashes, content) =>
|
|
126
|
+
when(hashes.length)({
|
|
127
|
+
1: () =>
|
|
128
|
+
`\n${chalk.yellow.bold('═══ ')}${chalk.yellow.bold(
|
|
129
|
+
content,
|
|
130
|
+
)}${chalk.yellow.bold(' ═══')}`,
|
|
131
|
+
2: () => chalk.yellowBright.bold(content),
|
|
132
|
+
3: () => chalk.yellow(content),
|
|
133
|
+
else: () => content,
|
|
134
|
+
}),
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
// Handle regular bold text after headings
|
|
138
|
+
const boldHandled = headingHandled.replace(/\*\*(.*?)\*\*/g, (_, content) =>
|
|
139
|
+
chalk.bold(content),
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
return boldHandled
|
|
126
143
|
}
|
|
127
144
|
|
|
128
|
-
async function analyzeInput(
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
145
|
+
async function analyzeInput(
|
|
146
|
+
input: string,
|
|
147
|
+
openAIKey: string,
|
|
148
|
+
useCrawling = false,
|
|
149
|
+
) {
|
|
150
|
+
const spinner = ora({
|
|
151
|
+
text: useCrawling ? 'Searching and analyzing...' : 'Thinking...',
|
|
152
|
+
spinner: {
|
|
153
|
+
frames: [
|
|
154
|
+
String.fromCodePoint(0x2819), // ⠙
|
|
155
|
+
String.fromCodePoint(0x2839), // ⠹
|
|
156
|
+
String.fromCodePoint(0x2838), // ⠸
|
|
157
|
+
String.fromCodePoint(0x2826), // ⠦
|
|
158
|
+
String.fromCodePoint(0x2807), // ⠧
|
|
159
|
+
String.fromCodePoint(0x280f), // ⠏
|
|
160
|
+
String.fromCodePoint(0x281f), // ⠟
|
|
161
|
+
String.fromCodePoint(0x283f), // ⠿
|
|
162
|
+
],
|
|
163
|
+
interval: 80,
|
|
164
|
+
},
|
|
165
|
+
}).start()
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
const result = await clai(input, openAIKey, useCrawling)
|
|
169
|
+
spinner.stop()
|
|
170
|
+
console.log(`${String.fromCodePoint(0x2705)} AHA!`)
|
|
171
|
+
|
|
172
|
+
console.log(
|
|
173
|
+
chalk.green.bold(`\n${String.fromCodePoint(0x1f4dd)} ═══ Summary ═══ :`),
|
|
174
|
+
)
|
|
175
|
+
const formattedContent = formatMarkdownForTerminal(result.summary)
|
|
176
|
+
await animateText(formattedContent)
|
|
177
|
+
|
|
178
|
+
// Prompt user to select a link
|
|
179
|
+
const { selectedLink } = await inquirer.prompt([
|
|
180
|
+
{
|
|
181
|
+
type: 'list',
|
|
182
|
+
name: 'selectedLink',
|
|
183
|
+
message: '\n\nWhat now?:',
|
|
184
|
+
choices: [
|
|
185
|
+
{
|
|
186
|
+
name: chalk.yellow(`${String.fromCodePoint(0x1f50d)} New search`),
|
|
187
|
+
value: 'new',
|
|
188
|
+
},
|
|
189
|
+
...result.links.map((link) => ({
|
|
190
|
+
name: `${chalk.bold(link.name)}: ${chalk.cyan(link.url)}`,
|
|
191
|
+
value: link.url,
|
|
192
|
+
})),
|
|
193
|
+
{ name: 'Exit', value: 'exit' },
|
|
194
|
+
],
|
|
195
|
+
prefix: String.fromCodePoint(0x003f), // ?
|
|
196
|
+
theme: {
|
|
197
|
+
prefix: String.fromCodePoint(0x003f), // ?
|
|
198
|
+
pointer: String.fromCodePoint(0x003e), // >
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
])
|
|
202
|
+
|
|
203
|
+
if (selectedLink === 'new') {
|
|
204
|
+
const { input: newInput } = await inquirer.prompt([
|
|
205
|
+
{
|
|
206
|
+
type: 'input',
|
|
207
|
+
name: 'input',
|
|
208
|
+
message: 'Enter a question or search query:',
|
|
209
|
+
validate: (input) => input.length > 0,
|
|
210
|
+
prefix: String.fromCodePoint(0x003f), // ?
|
|
211
|
+
theme: {
|
|
212
|
+
prefix: String.fromCodePoint(0x003f), // ?
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
])
|
|
216
|
+
await analyzeInput(newInput, openAIKey, useCrawling)
|
|
217
|
+
} else if (selectedLink && selectedLink !== 'exit') {
|
|
218
|
+
await analyzeInput(selectedLink, openAIKey, useCrawling)
|
|
219
|
+
}
|
|
220
|
+
} catch (error) {
|
|
221
|
+
spinner?.stop()
|
|
222
|
+
console.log(`${String.fromCodePoint(0x274c)} Analysis failed`)
|
|
223
|
+
console.error(chalk.red('Error:'), error)
|
|
224
|
+
}
|
|
198
225
|
}
|
|
199
226
|
|
|
200
|
-
main()
|
|
227
|
+
main()
|