@fettstorch/clai 0.1.4 → 0.1.6
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 +2 -2
- package/dist/cli.js +68 -10
- package/dist/index.js +4 -5
- package/package.json +1 -1
- package/src/cli.ts +18 -5
- package/src/scraper.ts +0 -3
- package/src/summarizer.ts +1 -0
- package/tsconfig.json +2 -1
package/README.md
CHANGED
@@ -33,9 +33,9 @@ clai "how tall can giraffes get?"
|
|
33
33
|
clai how tall can giraffes get
|
34
34
|
clai
|
35
35
|
```
|
36
|
-
<img width="
|
36
|
+
<img width="400" src="https://github.com/user-attachments/assets/002b3e05-5c77-4f4d-8aa3-ecb7412e9538" />
|
37
37
|
|
38
|
-
The
|
38
|
+
The cli expects either a URL or a search query or no argument at all.
|
39
39
|
When passing a search query without quotes make sure not to use any special characters, that might confuse the CLI e.g. ?
|
40
40
|
|
41
41
|
### Programmatic API
|
package/dist/cli.js
CHANGED
@@ -67837,8 +67837,6 @@ async function getGoogleResults(query) {
|
|
67837
67837
|
const urlLower = url.toLowerCase();
|
67838
67838
|
return !urlLower.includes("www.google") && !urlLower.includes("gstatic.com") && !urlLower.includes("googleapis.com") && !urlLower.includes("googleadservices") && queryWords.some((word) => urlLower.includes(word));
|
67839
67839
|
}));
|
67840
|
-
console.log("queryWords", queryWords);
|
67841
|
-
console.log("filteredUrls", filteredUrls);
|
67842
67840
|
const results = [...filteredUrls].slice(0, 3);
|
67843
67841
|
if (results.length === 0) {
|
67844
67842
|
throw new Error("No search results found");
|
@@ -67861,6 +67859,19 @@ function extractDataFromHtml(html3) {
|
|
67861
67859
|
url: cheerioDoc('link[rel="canonical"]').attr("href") || ""
|
67862
67860
|
};
|
67863
67861
|
}
|
67862
|
+
|
67863
|
+
// node_modules/@fettstorch/jule/dist/esm/when.js
|
67864
|
+
function when(c) {
|
67865
|
+
return (cases) => {
|
67866
|
+
const elseHandler = cases.else;
|
67867
|
+
const safeAccessor = c;
|
67868
|
+
const handler = safeAccessor in cases ? cases[safeAccessor] : elseHandler;
|
67869
|
+
if (typeof handler === "function") {
|
67870
|
+
return handler(c);
|
67871
|
+
}
|
67872
|
+
return handler;
|
67873
|
+
};
|
67874
|
+
}
|
67864
67875
|
// node_modules/@fettstorch/jule/dist/esm/observable.js
|
67865
67876
|
class Observable {
|
67866
67877
|
listeners = new Set;
|
@@ -72840,7 +72851,7 @@ function truncateContent(content) {
|
|
72840
72851
|
const maxChars = MAX_INPUT_TOKENS * 4;
|
72841
72852
|
if (content.length <= maxChars)
|
72842
72853
|
return content;
|
72843
|
-
return content.slice(0, maxChars)
|
72854
|
+
return content.slice(0, maxChars);
|
72844
72855
|
}
|
72845
72856
|
|
72846
72857
|
class OpenAIWrapper {
|
@@ -72862,8 +72873,8 @@ class OpenAIWrapper {
|
|
72862
72873
|
async completeStructured(prompt2, options) {
|
72863
72874
|
const truncatedPrompt = truncateContent(prompt2);
|
72864
72875
|
const {
|
72865
|
-
model = "gpt-
|
72866
|
-
temperature =
|
72876
|
+
model = "gpt-4o-mini",
|
72877
|
+
temperature = 1.6,
|
72867
72878
|
functionName = "generate_response",
|
72868
72879
|
responseSchema
|
72869
72880
|
} = options;
|
@@ -72914,6 +72925,7 @@ async function summarizeWebPage(content, openAIApiKey) {
|
|
72914
72925
|
- Step 2: Do that
|
72915
72926
|
- Step 3: Done
|
72916
72927
|
10. Mark proper nouns as bold e.g. **Harry Potter**
|
72928
|
+
11. Mark headings (h1, h2, h3) as #, ##, ### respectively
|
72917
72929
|
|
72918
72930
|
Don't just summarize, cite the key information.
|
72919
72931
|
|
@@ -72959,12 +72971,51 @@ async function clai(input, openAIKey) {
|
|
72959
72971
|
sources: scrapedData.map((data2) => data2.url)
|
72960
72972
|
};
|
72961
72973
|
}
|
72974
|
+
// package.json
|
72975
|
+
var package_default = {
|
72976
|
+
name: "@fettstorch/clai",
|
72977
|
+
version: "0.1.6",
|
72978
|
+
main: "dist/index.js",
|
72979
|
+
bin: {
|
72980
|
+
clai: "dist/cli.js"
|
72981
|
+
},
|
72982
|
+
repository: {
|
72983
|
+
type: "git",
|
72984
|
+
url: "git+https://github.com/schnullerpip/clai.git"
|
72985
|
+
},
|
72986
|
+
scripts: {
|
72987
|
+
start: "bun run src/cli.ts",
|
72988
|
+
build: "bun build ./src/index.ts --outdir dist --target node && bun build ./src/cli.ts --outdir dist --target node",
|
72989
|
+
dev: "bun --watch src/cli.ts"
|
72990
|
+
},
|
72991
|
+
author: "schnullerpip (https://github.com/schnullerpip)",
|
72992
|
+
license: "ISC",
|
72993
|
+
description: "AI-powered webpage summarizer",
|
72994
|
+
dependencies: {
|
72995
|
+
"@fettstorch/jule": "^0.5.3",
|
72996
|
+
chalk: "^5.3.0",
|
72997
|
+
cheerio: "^1.0.0-rc.12",
|
72998
|
+
commander: "^12.1.0",
|
72999
|
+
inquirer: "^12.1.0",
|
73000
|
+
openai: "^4.73.0",
|
73001
|
+
ora: "^8.1.1",
|
73002
|
+
googleapis: "^126.0.1"
|
73003
|
+
},
|
73004
|
+
devDependencies: {
|
73005
|
+
"@types/inquirer": "^9.0.7",
|
73006
|
+
"@types/node": "^20.11.19",
|
73007
|
+
"bun-types": "latest"
|
73008
|
+
},
|
73009
|
+
publishConfig: {
|
73010
|
+
access: "public"
|
73011
|
+
}
|
73012
|
+
};
|
72962
73013
|
|
72963
73014
|
// src/cli.ts
|
72964
73015
|
var program2 = new Command;
|
72965
73016
|
async function main2() {
|
72966
73017
|
try {
|
72967
|
-
program2.name("clai").description("AI-powered web scraping tool").version(
|
73018
|
+
program2.name("clai").description("AI-powered web scraping tool").version(package_default.version).argument("[input...]", "URL or search terms to analyze").action(async (inputs) => {
|
72968
73019
|
const openAIKey = process.env.OPENAI_API_KEY;
|
72969
73020
|
if (!openAIKey) {
|
72970
73021
|
console.error(source_default.red("\u274C OPENAI_API_KEY environment variable is not set"));
|
@@ -73019,15 +73070,22 @@ async function animateText(text3, delay = 25) {
|
|
73019
73070
|
process.stdout.write("\n");
|
73020
73071
|
}
|
73021
73072
|
function formatMarkdownForTerminal(text3) {
|
73022
|
-
|
73073
|
+
const headingHandled = text3.replace(/^(#{1,3})\s+(.*?)$/gm, (_3, hashes, content) => when(hashes.length)({
|
73074
|
+
1: () => `\n${source_default.yellow.bold("\u2550\u2550\u2550 ")}${source_default.yellow.bold(content)}${source_default.yellow.bold(" \u2550\u2550\u2550")}`,
|
73075
|
+
2: () => source_default.yellowBright.bold(content),
|
73076
|
+
3: () => source_default.yellow(content),
|
73077
|
+
else: () => content
|
73078
|
+
}));
|
73079
|
+
const boldHandled = headingHandled.replace(/\*\*(.*?)\*\*/g, (_3, content) => source_default.bold(content));
|
73080
|
+
return boldHandled;
|
73023
73081
|
}
|
73024
73082
|
async function analyzeInput(input, openAIKey) {
|
73025
|
-
const spinner = ora("
|
73083
|
+
const spinner = ora("Thinking...").start();
|
73026
73084
|
try {
|
73027
73085
|
const result = await clai(input, openAIKey);
|
73028
|
-
spinner.succeed("
|
73086
|
+
spinner.succeed("AHA!");
|
73029
73087
|
console.log(source_default.green.bold(`
|
73030
|
-
\uD83D\uDCDD Summary:`));
|
73088
|
+
\uD83D\uDCDD \u2550\u2550\u2550 Summary \u2550\u2550\u2550 :`));
|
73031
73089
|
const formattedContent = formatMarkdownForTerminal(result.summary);
|
73032
73090
|
await animateText(formattedContent);
|
73033
73091
|
const { selectedLink } = await esm_default12.prompt([
|
package/dist/index.js
CHANGED
@@ -41042,8 +41042,6 @@ async function getGoogleResults(query) {
|
|
41042
41042
|
const urlLower = url.toLowerCase();
|
41043
41043
|
return !urlLower.includes("www.google") && !urlLower.includes("gstatic.com") && !urlLower.includes("googleapis.com") && !urlLower.includes("googleadservices") && queryWords.some((word) => urlLower.includes(word));
|
41044
41044
|
}));
|
41045
|
-
console.log("queryWords", queryWords);
|
41046
|
-
console.log("filteredUrls", filteredUrls);
|
41047
41045
|
const results = [...filteredUrls].slice(0, 3);
|
41048
41046
|
if (results.length === 0) {
|
41049
41047
|
throw new Error("No search results found");
|
@@ -46045,7 +46043,7 @@ function truncateContent(content) {
|
|
46045
46043
|
const maxChars = MAX_INPUT_TOKENS * 4;
|
46046
46044
|
if (content.length <= maxChars)
|
46047
46045
|
return content;
|
46048
|
-
return content.slice(0, maxChars)
|
46046
|
+
return content.slice(0, maxChars);
|
46049
46047
|
}
|
46050
46048
|
|
46051
46049
|
class OpenAIWrapper {
|
@@ -46067,8 +46065,8 @@ class OpenAIWrapper {
|
|
46067
46065
|
async completeStructured(prompt, options) {
|
46068
46066
|
const truncatedPrompt = truncateContent(prompt);
|
46069
46067
|
const {
|
46070
|
-
model = "gpt-
|
46071
|
-
temperature =
|
46068
|
+
model = "gpt-4o-mini",
|
46069
|
+
temperature = 1.6,
|
46072
46070
|
functionName = "generate_response",
|
46073
46071
|
responseSchema
|
46074
46072
|
} = options;
|
@@ -46119,6 +46117,7 @@ async function summarizeWebPage(content, openAIApiKey) {
|
|
46119
46117
|
- Step 2: Do that
|
46120
46118
|
- Step 3: Done
|
46121
46119
|
10. Mark proper nouns as bold e.g. **Harry Potter**
|
46120
|
+
11. Mark headings (h1, h2, h3) as #, ##, ### respectively
|
46122
46121
|
|
46123
46122
|
Don't just summarize, cite the key information.
|
46124
46123
|
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
@@ -4,6 +4,8 @@ import inquirer from 'inquirer';
|
|
4
4
|
import chalk from 'chalk';
|
5
5
|
import ora from 'ora';
|
6
6
|
import { clai } from './index';
|
7
|
+
import { when } from '@fettstorch/jule';
|
8
|
+
import pkg from '../package.json' assert { type: 'json' };
|
7
9
|
|
8
10
|
const program = new Command();
|
9
11
|
|
@@ -12,7 +14,7 @@ async function main() {
|
|
12
14
|
program
|
13
15
|
.name('clai')
|
14
16
|
.description('AI-powered web scraping tool')
|
15
|
-
.version(
|
17
|
+
.version(pkg.version)
|
16
18
|
.argument('[input...]', 'URL or search terms to analyze')
|
17
19
|
.action(async (inputs: string[]) => {
|
18
20
|
const openAIKey = process.env.OPENAI_API_KEY;
|
@@ -88,17 +90,28 @@ async function animateText(text: string, delay = 25) {
|
|
88
90
|
}
|
89
91
|
|
90
92
|
function formatMarkdownForTerminal(text: string): string {
|
91
|
-
|
93
|
+
// Handle headings first
|
94
|
+
const headingHandled = text.replace(/^(#{1,3})\s+(.*?)$/gm, (_, hashes, content) => when(hashes.length)({
|
95
|
+
1: () => `\n${chalk.yellow.bold('═══ ')}${chalk.yellow.bold(content)}${chalk.yellow.bold(' ═══')}`,
|
96
|
+
2: () => chalk.yellowBright.bold(content),
|
97
|
+
3: () => chalk.yellow(content),
|
98
|
+
else: () => content
|
99
|
+
}));
|
100
|
+
|
101
|
+
// Handle regular bold text after headings
|
102
|
+
const boldHandled = headingHandled.replace(/\*\*(.*?)\*\*/g, (_, content) => chalk.bold(content));
|
103
|
+
|
104
|
+
return boldHandled;
|
92
105
|
}
|
93
106
|
|
94
107
|
async function analyzeInput(input: string, openAIKey: string) {
|
95
|
-
const spinner = ora('
|
108
|
+
const spinner = ora('Thinking...').start();
|
96
109
|
|
97
110
|
try {
|
98
111
|
const result = await clai(input, openAIKey);
|
99
|
-
spinner.succeed('
|
112
|
+
spinner.succeed('AHA!');
|
100
113
|
|
101
|
-
console.log(chalk.green.bold('\n📝 Summary:'));
|
114
|
+
console.log(chalk.green.bold('\n📝 ═══ Summary ═══ :'));
|
102
115
|
const formattedContent = formatMarkdownForTerminal(result.summary);
|
103
116
|
await animateText(formattedContent);
|
104
117
|
|
package/src/scraper.ts
CHANGED
package/src/summarizer.ts
CHANGED
@@ -41,6 +41,7 @@ export async function summarizeWebPage(content: string, openAIApiKey: string): P
|
|
41
41
|
- Step 2: Do that
|
42
42
|
- Step 3: Done
|
43
43
|
10. Mark proper nouns as bold e.g. **Harry Potter**
|
44
|
+
11. Mark headings (h1, h2, h3) as #, ##, ### respectively
|
44
45
|
|
45
46
|
Don't just summarize, cite the key information.
|
46
47
|
|