@fettstorch/clai 0.1.3 → 0.1.5
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/dist/cli.js +78 -10
- package/dist/index.js +10 -5
- package/package.json +1 -1
- package/src/cli.ts +23 -5
- package/src/openai.ts +3 -3
- package/src/scraper.ts +6 -1
- package/src/summarizer.ts +4 -1
- package/tsconfig.json +2 -1
package/dist/cli.js
CHANGED
@@ -67816,7 +67816,10 @@ async function scrape(input) {
|
|
67816
67816
|
}
|
67817
67817
|
}
|
67818
67818
|
function isValidUrl(input) {
|
67819
|
-
|
67819
|
+
if (input.includes(" "))
|
67820
|
+
return false;
|
67821
|
+
const tldPattern = /^[^\s]+\.[a-z]{2,}$/i;
|
67822
|
+
return tldPattern.test(input);
|
67820
67823
|
}
|
67821
67824
|
function normalizeUrl(url) {
|
67822
67825
|
if (!url.startsWith("http://") && !url.startsWith("https://")) {
|
@@ -67856,6 +67859,19 @@ function extractDataFromHtml(html3) {
|
|
67856
67859
|
url: cheerioDoc('link[rel="canonical"]').attr("href") || ""
|
67857
67860
|
};
|
67858
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
|
+
}
|
67859
67875
|
// node_modules/@fettstorch/jule/dist/esm/observable.js
|
67860
67876
|
class Observable {
|
67861
67877
|
listeners = new Set;
|
@@ -72835,7 +72851,7 @@ function truncateContent(content) {
|
|
72835
72851
|
const maxChars = MAX_INPUT_TOKENS * 4;
|
72836
72852
|
if (content.length <= maxChars)
|
72837
72853
|
return content;
|
72838
|
-
return content.slice(0, maxChars)
|
72854
|
+
return content.slice(0, maxChars);
|
72839
72855
|
}
|
72840
72856
|
|
72841
72857
|
class OpenAIWrapper {
|
@@ -72857,8 +72873,8 @@ class OpenAIWrapper {
|
|
72857
72873
|
async completeStructured(prompt2, options) {
|
72858
72874
|
const truncatedPrompt = truncateContent(prompt2);
|
72859
72875
|
const {
|
72860
|
-
model = "gpt-
|
72861
|
-
temperature =
|
72876
|
+
model = "gpt-4o-mini",
|
72877
|
+
temperature = 1.6,
|
72862
72878
|
functionName = "generate_response",
|
72863
72879
|
responseSchema
|
72864
72880
|
} = options;
|
@@ -72909,6 +72925,7 @@ async function summarizeWebPage(content, openAIApiKey) {
|
|
72909
72925
|
- Step 2: Do that
|
72910
72926
|
- Step 3: Done
|
72911
72927
|
10. Mark proper nouns as bold e.g. **Harry Potter**
|
72928
|
+
11. Mark headings (h1, h2, h3) as #, ##, ### respectively
|
72912
72929
|
|
72913
72930
|
Don't just summarize, cite the key information.
|
72914
72931
|
|
@@ -72936,10 +72953,11 @@ async function summarizeWebPage(content, openAIApiKey) {
|
|
72936
72953
|
}
|
72937
72954
|
}
|
72938
72955
|
};
|
72939
|
-
|
72956
|
+
const result = await openai.completeStructured(prompt2, {
|
72940
72957
|
temperature: 0.3,
|
72941
72958
|
responseSchema: schema
|
72942
72959
|
});
|
72960
|
+
return result;
|
72943
72961
|
}
|
72944
72962
|
|
72945
72963
|
// src/index.ts
|
@@ -72953,12 +72971,51 @@ async function clai(input, openAIKey) {
|
|
72953
72971
|
sources: scrapedData.map((data2) => data2.url)
|
72954
72972
|
};
|
72955
72973
|
}
|
72974
|
+
// package.json
|
72975
|
+
var package_default = {
|
72976
|
+
name: "@fettstorch/clai",
|
72977
|
+
version: "0.1.5",
|
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
|
+
};
|
72956
73013
|
|
72957
73014
|
// src/cli.ts
|
72958
73015
|
var program2 = new Command;
|
72959
73016
|
async function main2() {
|
72960
73017
|
try {
|
72961
|
-
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) => {
|
72962
73019
|
const openAIKey = process.env.OPENAI_API_KEY;
|
72963
73020
|
if (!openAIKey) {
|
72964
73021
|
console.error(source_default.red("\u274C OPENAI_API_KEY environment variable is not set"));
|
@@ -73012,14 +73069,25 @@ async function animateText(text3, delay = 25) {
|
|
73012
73069
|
process.stdin.removeListener("keypress", keypressHandler);
|
73013
73070
|
process.stdout.write("\n");
|
73014
73071
|
}
|
73072
|
+
function formatMarkdownForTerminal(text3) {
|
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;
|
73081
|
+
}
|
73015
73082
|
async function analyzeInput(input, openAIKey) {
|
73016
|
-
const spinner = ora("
|
73083
|
+
const spinner = ora("Thinking...").start();
|
73017
73084
|
try {
|
73018
73085
|
const result = await clai(input, openAIKey);
|
73019
|
-
spinner.succeed("
|
73086
|
+
spinner.succeed("AHA!");
|
73020
73087
|
console.log(source_default.green.bold(`
|
73021
|
-
\uD83D\uDCDD Summary:`));
|
73022
|
-
|
73088
|
+
\uD83D\uDCDD \u2550\u2550\u2550 Summary \u2550\u2550\u2550 :`));
|
73089
|
+
const formattedContent = formatMarkdownForTerminal(result.summary);
|
73090
|
+
await animateText(formattedContent);
|
73023
73091
|
const { selectedLink } = await esm_default12.prompt([
|
73024
73092
|
{
|
73025
73093
|
type: "list",
|
package/dist/index.js
CHANGED
@@ -41021,7 +41021,10 @@ async function scrape(input) {
|
|
41021
41021
|
}
|
41022
41022
|
}
|
41023
41023
|
function isValidUrl(input) {
|
41024
|
-
|
41024
|
+
if (input.includes(" "))
|
41025
|
+
return false;
|
41026
|
+
const tldPattern = /^[^\s]+\.[a-z]{2,}$/i;
|
41027
|
+
return tldPattern.test(input);
|
41025
41028
|
}
|
41026
41029
|
function normalizeUrl(url) {
|
41027
41030
|
if (!url.startsWith("http://") && !url.startsWith("https://")) {
|
@@ -46040,7 +46043,7 @@ function truncateContent(content) {
|
|
46040
46043
|
const maxChars = MAX_INPUT_TOKENS * 4;
|
46041
46044
|
if (content.length <= maxChars)
|
46042
46045
|
return content;
|
46043
|
-
return content.slice(0, maxChars)
|
46046
|
+
return content.slice(0, maxChars);
|
46044
46047
|
}
|
46045
46048
|
|
46046
46049
|
class OpenAIWrapper {
|
@@ -46062,8 +46065,8 @@ class OpenAIWrapper {
|
|
46062
46065
|
async completeStructured(prompt, options) {
|
46063
46066
|
const truncatedPrompt = truncateContent(prompt);
|
46064
46067
|
const {
|
46065
|
-
model = "gpt-
|
46066
|
-
temperature =
|
46068
|
+
model = "gpt-4o-mini",
|
46069
|
+
temperature = 1.6,
|
46067
46070
|
functionName = "generate_response",
|
46068
46071
|
responseSchema
|
46069
46072
|
} = options;
|
@@ -46114,6 +46117,7 @@ async function summarizeWebPage(content, openAIApiKey) {
|
|
46114
46117
|
- Step 2: Do that
|
46115
46118
|
- Step 3: Done
|
46116
46119
|
10. Mark proper nouns as bold e.g. **Harry Potter**
|
46120
|
+
11. Mark headings (h1, h2, h3) as #, ##, ### respectively
|
46117
46121
|
|
46118
46122
|
Don't just summarize, cite the key information.
|
46119
46123
|
|
@@ -46141,10 +46145,11 @@ async function summarizeWebPage(content, openAIApiKey) {
|
|
46141
46145
|
}
|
46142
46146
|
}
|
46143
46147
|
};
|
46144
|
-
|
46148
|
+
const result = await openai.completeStructured(prompt, {
|
46145
46149
|
temperature: 0.3,
|
46146
46150
|
responseSchema: schema
|
46147
46151
|
});
|
46152
|
+
return result;
|
46148
46153
|
}
|
46149
46154
|
|
46150
46155
|
// src/index.ts
|
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;
|
@@ -87,15 +89,31 @@ async function animateText(text: string, delay = 25) {
|
|
87
89
|
process.stdout.write('\n');
|
88
90
|
}
|
89
91
|
|
92
|
+
function formatMarkdownForTerminal(text: string): string {
|
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;
|
105
|
+
}
|
106
|
+
|
90
107
|
async function analyzeInput(input: string, openAIKey: string) {
|
91
|
-
const spinner = ora('
|
108
|
+
const spinner = ora('Thinking...').start();
|
92
109
|
|
93
110
|
try {
|
94
111
|
const result = await clai(input, openAIKey);
|
95
|
-
spinner.succeed('
|
112
|
+
spinner.succeed('AHA!');
|
96
113
|
|
97
|
-
console.log(chalk.green.bold('\n📝 Summary:'));
|
98
|
-
|
114
|
+
console.log(chalk.green.bold('\n📝 ═══ Summary ═══ :'));
|
115
|
+
const formattedContent = formatMarkdownForTerminal(result.summary);
|
116
|
+
await animateText(formattedContent);
|
99
117
|
|
100
118
|
// Prompt user to select a link
|
101
119
|
const { selectedLink } = await inquirer.prompt([
|
package/src/openai.ts
CHANGED
@@ -6,7 +6,7 @@ const MAX_INPUT_TOKENS = 10000;
|
|
6
6
|
function truncateContent(content: string): string {
|
7
7
|
const maxChars = MAX_INPUT_TOKENS * 4;
|
8
8
|
if (content.length <= maxChars) return content;
|
9
|
-
return content.slice(0, maxChars)
|
9
|
+
return content.slice(0, maxChars);
|
10
10
|
}
|
11
11
|
|
12
12
|
export interface StructuredResponse<T> {
|
@@ -53,8 +53,8 @@ class OpenAIWrapper {
|
|
53
53
|
): Promise<T> {
|
54
54
|
const truncatedPrompt = truncateContent(prompt);
|
55
55
|
const {
|
56
|
-
model = 'gpt-
|
57
|
-
temperature =
|
56
|
+
model = 'gpt-4o-mini',
|
57
|
+
temperature = 1.6,
|
58
58
|
functionName = 'generate_response',
|
59
59
|
responseSchema
|
60
60
|
} = options;
|
package/src/scraper.ts
CHANGED
@@ -41,7 +41,12 @@ export async function scrape(input: string): Promise<ScrapedData[]> {
|
|
41
41
|
// --- module private
|
42
42
|
|
43
43
|
function isValidUrl(input: string): boolean {
|
44
|
-
|
44
|
+
// Check for whitespace
|
45
|
+
if (input.includes(' ')) return false;
|
46
|
+
|
47
|
+
// Check for common TLDs using regex
|
48
|
+
const tldPattern = /^[^\s]+\.[a-z]{2,}$/i;
|
49
|
+
return tldPattern.test(input);
|
45
50
|
}
|
46
51
|
|
47
52
|
function normalizeUrl(url: string): string {
|
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
|
|
@@ -70,8 +71,10 @@ export async function summarizeWebPage(content: string, openAIApiKey: string): P
|
|
70
71
|
}
|
71
72
|
};
|
72
73
|
|
73
|
-
|
74
|
+
const result = await openai.completeStructured<SummaryResult>(prompt, {
|
74
75
|
temperature: 0.3,
|
75
76
|
responseSchema: schema
|
76
77
|
});
|
78
|
+
|
79
|
+
return result;
|
77
80
|
}
|