@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 CHANGED
@@ -67816,7 +67816,10 @@ async function scrape(input) {
67816
67816
  }
67817
67817
  }
67818
67818
  function isValidUrl(input) {
67819
- return !input.includes(" ");
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) + "... (content truncated)";
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-3.5-turbo",
72861
- temperature = 0.6,
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
- return openai.completeStructured(prompt2, {
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("1.0.0").argument("[input...]", "URL or search terms to analyze").action(async (inputs) => {
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("Analyzing content...").start();
73083
+ const spinner = ora("Thinking...").start();
73017
73084
  try {
73018
73085
  const result = await clai(input, openAIKey);
73019
- spinner.succeed("Analysis complete");
73086
+ spinner.succeed("AHA!");
73020
73087
  console.log(source_default.green.bold(`
73021
- \uD83D\uDCDD Summary:`));
73022
- await animateText(result.summary);
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
- return !input.includes(" ");
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) + "... (content truncated)";
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-3.5-turbo",
46066
- temperature = 0.6,
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
- return openai.completeStructured(prompt, {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fettstorch/clai",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "main": "dist/index.js",
5
5
  "bin": {
6
6
  "clai": "dist/cli.js"
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('1.0.0')
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('Analyzing content...').start();
108
+ const spinner = ora('Thinking...').start();
92
109
 
93
110
  try {
94
111
  const result = await clai(input, openAIKey);
95
- spinner.succeed('Analysis complete');
112
+ spinner.succeed('AHA!');
96
113
 
97
- console.log(chalk.green.bold('\n📝 Summary:'));
98
- await animateText(result.summary);
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) + '... (content truncated)';
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-3.5-turbo',
57
- temperature = 0.6,
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
- return !input.includes(' ');
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
- return openai.completeStructured<SummaryResult>(prompt, {
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
  }
package/tsconfig.json CHANGED
@@ -8,7 +8,8 @@
8
8
  "rootDir": "./src",
9
9
  "strict": true,
10
10
  "skipLibCheck": true,
11
- "forceConsistentCasingInFileNames": true
11
+ "forceConsistentCasingInFileNames": true,
12
+ "resolveJsonModule": true
12
13
  },
13
14
  "include": ["src/**/*"]
14
15
  }