@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.
@@ -13,7 +13,9 @@
13
13
  "Bash(bun:*)",
14
14
  "Bash(timeout:*)",
15
15
  "Bash(git add:*)",
16
- "Bash(git commit:*)"
16
+ "Bash(git commit:*)",
17
+ "Bash(git push:*)",
18
+ "Bash(npm publish:*)"
17
19
  ],
18
20
  "deny": []
19
21
  }
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Command Line AI Interface (CLAI)
2
2
 
3
- A tool for AI powered web search/scrape and summarization in the Terminal.
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
- ### CLI Usage
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 cli expects either a URL or a search query or no argument at all.
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
- const { summary, links, sources } = await clai('https://example.com', 'your-openai-api-key');
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
- - Needs a better prompt in order to more reliably stop narrating a page's content and rather cite it in a more concise manner.
50
- - large sites might surpass the token limit (currently not handled gracefully)
51
- - occasionally clai doesn't find search results (this is not handled gracefully)
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.15";
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 - using OpenAI directly for query...");
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 web scraping tool").version(package_default.version).argument("[input...]", "URL or search terms to analyze").action(async (inputs) => {
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 URL or search query:",
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
- { name: source_default.yellow(`${String.fromCodePoint(128269)} New search`), value: "new" },
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 URL or search query:",
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 - using OpenAI directly for query...");
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fettstorch/clai",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
4
4
  "main": "dist/index.js",
5
5
  "bin": {
6
6
  "clai": "dist/cli.js"
package/src/cli.ts CHANGED
@@ -1,200 +1,227 @@
1
1
  #!/usr/bin/env bun
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";
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
- console.log(`[clAi]::${chalk.cyan(version)}`);
15
- try {
16
- program
17
- .name("clai")
18
- .description("AI-powered web scraping tool")
19
- .version(pkg.version)
20
- .argument("[input...]", "URL or search terms to analyze")
21
- .action(async (inputs: string[]) => {
22
- const openAIKey = process.env.OPENAI_API_KEY;
23
-
24
- if (!openAIKey) {
25
- console.error(
26
- chalk.red(`${String.fromCodePoint(0x274c)} OPENAI_API_KEY environment variable is not set`)
27
- );
28
- process.exit(1);
29
- }
30
-
31
- let input = inputs?.join(" ");
32
-
33
- if (!input) {
34
- const answers = await inquirer.prompt([
35
- {
36
- type: "input",
37
- name: "input",
38
- message: "Enter a URL or search query:",
39
- validate: (input) => input.length > 0,
40
- prefix: String.fromCodePoint(0x003F), // ?
41
- theme: {
42
- prefix: String.fromCodePoint(0x003F) // ?
43
- }
44
- },
45
- ]);
46
- input = answers.input;
47
- }
48
-
49
- await analyzeInput(input, openAIKey);
50
- process.exit(0);
51
- });
52
-
53
- await program.parseAsync();
54
- } catch (error) {
55
- console.error(chalk.red("Fatal error:"), error);
56
- process.exit(1);
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
- let shouldComplete = false;
62
-
63
- // Setup keypress listener
64
- const keypressHandler = (str: string, key: { name: string }) => {
65
- if (key.name === "return") {
66
- shouldComplete = true;
67
- }
68
- };
69
-
70
- process.stdin.on("keypress", keypressHandler);
71
-
72
- // Enable raw mode to get keypress events
73
- process.stdin.setRawMode(true);
74
- process.stdin.resume();
75
-
76
- // Small delay to ensure stdin is ready for keypress capture
77
- // This fixes the issue where the first animation can't be interrupted
78
- await new Promise(resolve => setTimeout(resolve, 50));
79
-
80
- let currentIndex = 0;
81
- while (currentIndex < text.length) {
82
- if (shouldComplete) {
83
- // Show remaining text immediately
84
- process.stdout.write(text.slice(currentIndex));
85
- break;
86
- }
87
-
88
- process.stdout.write(text[currentIndex]);
89
- currentIndex++;
90
-
91
- if (!shouldComplete) {
92
- await new Promise((resolve) => setTimeout(resolve, delay));
93
- }
94
- }
95
-
96
- // Cleanup
97
- process.stdin.setRawMode(false);
98
- process.stdin.pause();
99
- process.stdin.removeListener("keypress", keypressHandler);
100
-
101
- process.stdout.write("\n");
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
- // Handle headings first
106
- const headingHandled = text.replace(
107
- /^(#{1,3})\s+(.*?)$/gm,
108
- (_, hashes, content) =>
109
- when(hashes.length)({
110
- 1: () =>
111
- `\n${chalk.yellow.bold("═══ ")}${chalk.yellow.bold(
112
- content
113
- )}${chalk.yellow.bold(" ═══")}`,
114
- 2: () => chalk.yellowBright.bold(content),
115
- 3: () => chalk.yellow(content),
116
- else: () => content,
117
- })
118
- );
119
-
120
- // Handle regular bold text after headings
121
- const boldHandled = headingHandled.replace(/\*\*(.*?)\*\*/g, (_, content) =>
122
- chalk.bold(content)
123
- );
124
-
125
- return boldHandled;
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(input: string, openAIKey: string) {
129
- const spinner = ora({
130
- text: "Thinking...",
131
- spinner: {
132
- frames: [
133
- String.fromCodePoint(0x2819), //
134
- String.fromCodePoint(0x2839), //
135
- String.fromCodePoint(0x2838), // ⠸
136
- String.fromCodePoint(0x2826), // ⠦
137
- String.fromCodePoint(0x2807), //
138
- String.fromCodePoint(0x280F), //
139
- String.fromCodePoint(0x281F), //
140
- String.fromCodePoint(0x283F), //
141
- ],
142
- interval: 80
143
- }
144
- }).start();
145
-
146
- try {
147
- const result = await clai(input, openAIKey);
148
- spinner.stop();
149
- console.log(`${String.fromCodePoint(0x2705)} AHA!`);
150
-
151
- console.log(chalk.green.bold(`\n${String.fromCodePoint(0x1f4dd)} ═══ Summary ═══ :`));
152
- const formattedContent = formatMarkdownForTerminal(result.summary);
153
- await animateText(formattedContent);
154
-
155
- // Prompt user to select a link
156
- const { selectedLink } = await inquirer.prompt([
157
- {
158
- type: "list",
159
- name: "selectedLink",
160
- message: "\n\nWhat now?:",
161
- choices: [
162
- { name: chalk.yellow(`${String.fromCodePoint(0x1f50d)} New search`), value: "new" },
163
- ...result.links.map((link) => ({
164
- name: `${chalk.bold(link.name)}: ${chalk.cyan(link.url)}`,
165
- value: link.url,
166
- })),
167
- { name: "Exit", value: "exit" },
168
- ],
169
- prefix: String.fromCodePoint(0x003F), // ?
170
- theme: {
171
- prefix: String.fromCodePoint(0x003F) // ?
172
- }
173
- },
174
- ]);
175
-
176
- if (selectedLink === "new") {
177
- const { input: newInput } = await inquirer.prompt([
178
- {
179
- type: "input",
180
- name: "input",
181
- message: "Enter a URL or search query:",
182
- validate: (input) => input.length > 0,
183
- prefix: String.fromCodePoint(0x003F), // ?
184
- theme: {
185
- prefix: String.fromCodePoint(0x003F) // ?
186
- }
187
- },
188
- ]);
189
- await analyzeInput(newInput, openAIKey);
190
- } else if (selectedLink && selectedLink !== "exit") {
191
- await analyzeInput(selectedLink, openAIKey);
192
- }
193
- } catch (error) {
194
- spinner?.stop();
195
- console.log(`${String.fromCodePoint(0x274c)} Analysis failed`);
196
- console.error(chalk.red("Error:"), error);
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()