ask-my-llm 1.0.6 → 1.0.8

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.
Files changed (4) hide show
  1. package/README.md +12 -1
  2. package/bin/ask-ai +88 -11
  3. package/index.js +63 -6
  4. package/package.json +1 -1
package/README.md CHANGED
@@ -24,10 +24,14 @@ Config saved to `~/.askairc`.
24
24
  ## Usage
25
25
 
26
26
  ```js
27
- const { ask } = require("ask-my-llm");
27
+ const { ask, askAsync } = require("ask-my-llm");
28
28
 
29
+ // sync
29
30
  const response = ask("Hello");
30
31
  console.log(response);
32
+
33
+ // async
34
+ askAsync("Hello").then(console.log);
31
35
  ```
32
36
 
33
37
  ## API
@@ -38,3 +42,10 @@ Sends a prompt to the configured AI and returns the response synchronously.
38
42
 
39
43
  - `prompt` - The message to send
40
44
  - Returns: AI response as string
45
+
46
+ ### askAsync(prompt: string): Promise<string>
47
+
48
+ Sends a prompt to the configured AI and returns a Promise.
49
+
50
+ - `prompt` - The message to send
51
+ - Returns: Promise that resolves to AI response as string
package/bin/ask-ai CHANGED
@@ -10,36 +10,113 @@ function loadConfig() {
10
10
  try {
11
11
  return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
12
12
  } catch (e) {
13
- return { baseApi: 'https://api.openai.com/v1', apiKey: '', model: 'gpt-3.5-turbo' };
13
+ return { current: 'default', configs: { default: { baseApi: 'https://api.openai.com/v1', apiKey: '', model: 'gpt-3.5-turbo' } } };
14
14
  }
15
15
  }
16
16
 
17
- async function setup() {
18
- const existing = loadConfig();
17
+ function saveConfig(config) {
18
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
19
+ }
20
+
21
+ async function mainMenu() {
22
+ const config = loadConfig();
23
+ const choices = ['Add new config', 'Select config', 'Remove config'];
19
24
 
25
+ if (config.current) {
26
+ choices.unshift(`Use current (${config.current})`);
27
+ }
28
+
29
+ const { action } = await inquirer.prompt([{
30
+ type: 'list',
31
+ name: 'action',
32
+ message: 'Manage configs:',
33
+ choices
34
+ }]);
35
+
36
+ if (action === 'Add new config') {
37
+ await addConfig(config);
38
+ } else if (action === 'Select config') {
39
+ await selectConfig(config);
40
+ } else if (action === 'Remove config') {
41
+ await removeConfig(config);
42
+ } else {
43
+ console.log(`Current: ${config.current}`);
44
+ }
45
+ }
46
+
47
+ async function addConfig(config) {
48
+ const { name } = await inquirer.prompt([{
49
+ type: 'input',
50
+ name: 'name',
51
+ message: 'Config name:'
52
+ }]);
53
+
54
+ if (config.configs[name]) {
55
+ console.log('Config already exists. Edit it instead.');
56
+ return;
57
+ }
58
+
20
59
  const answers = await inquirer.prompt([
21
60
  {
22
61
  type: 'input',
23
62
  name: 'baseApi',
24
- message: 'Base API URL (e.g., https://api.openai.com/v1):',
25
- default: existing.baseApi
63
+ message: 'Base API URL:',
64
+ default: 'https://api.openai.com/v1'
26
65
  },
27
66
  {
28
67
  type: 'input',
29
68
  name: 'apiKey',
30
69
  message: 'API Key (leave empty for none):',
31
- default: existing.apiKey || ''
70
+ default: ''
32
71
  },
33
72
  {
34
73
  type: 'input',
35
74
  name: 'model',
36
- message: 'Model (e.g., gpt-3.5-turbo):',
37
- default: existing.model
75
+ message: 'Model:',
76
+ default: 'gpt-3.5-turbo'
38
77
  }
39
78
  ]);
40
79
 
41
- fs.writeFileSync(CONFIG_PATH, JSON.stringify(answers, null, 2));
42
- console.log('Configuration saved to ~/.askairc');
80
+ config.configs[name] = answers;
81
+ config.current = name;
82
+ saveConfig(config);
83
+ console.log(`Config '${name}' saved.`);
84
+ }
85
+
86
+ async function selectConfig(config) {
87
+ const names = Object.keys(config.configs);
88
+ const { name } = await inquirer.prompt([{
89
+ type: 'list',
90
+ name: 'name',
91
+ message: 'Select config:',
92
+ choices: names
93
+ }]);
94
+
95
+ config.current = name;
96
+ saveConfig(config);
97
+ console.log(`Using '${name}'.`);
98
+ }
99
+
100
+ async function removeConfig(config) {
101
+ const names = Object.keys(config.configs);
102
+ if (names.length <= 1) {
103
+ console.log('Cannot remove last config.');
104
+ return;
105
+ }
106
+
107
+ const { name } = await inquirer.prompt([{
108
+ type: 'list',
109
+ name: 'name',
110
+ message: 'Remove config:',
111
+ choices: names
112
+ }]);
113
+
114
+ delete config.configs[name];
115
+ if (config.current === name) {
116
+ config.current = Object.keys(config.configs)[0];
117
+ }
118
+ saveConfig(config);
119
+ console.log(`Config '${name}' removed.`);
43
120
  }
44
121
 
45
- setup();
122
+ mainMenu();
package/index.js CHANGED
@@ -1,4 +1,4 @@
1
- const { spawnSync } = require('child_process');
1
+ const { spawnSync, spawn } = require('child_process');
2
2
  const fs = require('fs');
3
3
  const path = require('path');
4
4
 
@@ -6,9 +6,14 @@ const CONFIG_PATH = path.join(process.env.HOME || process.env.USERPROFILE, '.ask
6
6
 
7
7
  function loadConfig() {
8
8
  try {
9
- return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
9
+ const data = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
10
+ const current = data.current || 'default';
11
+ if (!data.configs || !data.configs[current]) {
12
+ throw new Error('No config found. Run `npx ask-my-llm` first.');
13
+ }
14
+ return data.configs[current];
10
15
  } catch (e) {
11
- throw new Error('Not configured. Run `npx ask-ai` first.');
16
+ throw new Error('Not configured. Run `npx ask-my-llm` first.');
12
17
  }
13
18
  }
14
19
 
@@ -48,12 +53,64 @@ function ask(prompt) {
48
53
  if (result.error) throw result.error;
49
54
  if (result.status !== 0) throw new Error('curl failed: ' + (result.stderr || result.stdout));
50
55
 
51
- const parsed = JSON.parse(result.stdout);
56
+ return parseResponse(result.stdout);
57
+ }
58
+
59
+ function askAsync(prompt) {
60
+ return new Promise((resolve, reject) => {
61
+ const config = loadConfig();
62
+
63
+ const postData = JSON.stringify({
64
+ model: config.model,
65
+ messages: [{ role: 'user', content: prompt }],
66
+ temperature: 0.7
67
+ });
68
+
69
+ let baseUrl = config.baseApi.replace(/\/$/, '');
70
+ if (!baseUrl.endsWith('/v1')) {
71
+ baseUrl += '/v1';
72
+ }
73
+ const url = baseUrl + '/chat/completions';
74
+
75
+ const headerArgs = [
76
+ '-s', '-S', '-X', 'POST',
77
+ '-H', 'Content-Type: application/json',
78
+ '-H', 'HTTP-Referer: https://cows.info.gf',
79
+ '-H', 'X-OpenRouter-Title: ask-my-llm'
80
+ ];
81
+ if (config.apiKey) {
82
+ headerArgs.push('-H', `Authorization: Bearer ${config.apiKey}`);
83
+ }
84
+ headerArgs.push('-d', postData, url);
85
+
86
+ const child = spawn('curl', headerArgs, { encoding: 'utf8' });
87
+ let data = '';
88
+ let error = '';
89
+
90
+ child.stdout.on('data', chunk => data += chunk);
91
+ child.stderr.on('data', chunk => error += chunk);
92
+ child.on('close', code => {
93
+ if (code !== 0) {
94
+ reject(new Error('curl failed: ' + (error || data)));
95
+ } else {
96
+ try {
97
+ resolve(parseResponse(data));
98
+ } catch (e) {
99
+ reject(e);
100
+ }
101
+ }
102
+ });
103
+ child.on('error', reject);
104
+ });
105
+ }
106
+
107
+ function parseResponse(stdout) {
108
+ const parsed = JSON.parse(stdout);
52
109
  if (parsed.choices && parsed.choices[0]) {
53
110
  return parsed.choices[0].message.content;
54
111
  } else {
55
- throw new Error('API Error: ' + result.stdout);
112
+ throw new Error('API Error: ' + stdout);
56
113
  }
57
114
  }
58
115
 
59
- module.exports = { ask };
116
+ module.exports = { ask, askAsync };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ask-my-llm",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "Oversimplified AI usage npm module",
5
5
  "main": "index.js",
6
6
  "bin": {