igris-soul 1.1.0 → 1.1.2

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 (3) hide show
  1. package/cli.cjs +23 -0
  2. package/index.js +130 -74
  3. package/package.json +9 -7
package/cli.cjs ADDED
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { spawn } = require("node:child_process");
4
+ const { fileURLToPath } = require("node:url");
5
+ const path = require("node:path");
6
+
7
+ const packageDir = path.dirname(
8
+ fileURLToPath(require.resolve("./package.json")),
9
+ );
10
+
11
+ const indexPath = path.join(packageDir, "index.js");
12
+
13
+ const child = spawn(process.execPath, [indexPath, ...process.argv.slice(2)], {
14
+ stdio: "inherit",
15
+ });
16
+
17
+ child.on("exit", (code, signal) => {
18
+ if (signal) {
19
+ process.kill(process.pid, signal);
20
+ } else {
21
+ process.exit(code ?? 0);
22
+ }
23
+ });
package/index.js CHANGED
@@ -10,6 +10,7 @@ import {
10
10
  import { homedir } from "node:os";
11
11
  import { join } from "node:path";
12
12
  import rl from "readline/promises";
13
+
13
14
  import {
14
15
  HumanMessage,
15
16
  AIMessage,
@@ -18,81 +19,102 @@ import {
18
19
  createAgent,
19
20
  AIMessageChunk,
20
21
  } from "langchain";
22
+
21
23
  import * as z from "zod";
22
24
 
23
25
  /* =========================================================
24
- IGRIS CONFIGURATION
26
+ IGRIS DIRECTORIES & FILES
25
27
  ========================================================= */
26
28
 
27
29
  const igrisDir = join(homedir(), ".igris");
28
30
  const envPath = join(igrisDir, ".env");
29
31
  const userDataPath = join(igrisDir, "user.json");
30
32
 
31
- // Create ~/.igris automatically
33
+ /* =========================================================
34
+ READLINE
35
+ ========================================================= */
36
+
37
+ const readline = rl.createInterface({
38
+ input: process.stdin,
39
+ output: process.stdout,
40
+ });
41
+
42
+ /* =========================================================
43
+ CREATE IGRIS DIRECTORY
44
+ ========================================================= */
45
+
32
46
  await mkdir(igrisDir, { recursive: true });
33
47
 
34
- // Check whether .env exists
48
+ /* =========================================================
49
+ CREATE .ENV IF IT DOESN'T EXIST
50
+ ========================================================= */
51
+
35
52
  async function ensureEnvFile() {
36
53
  try {
37
54
  await access(envPath);
38
55
  } catch {
39
56
  await writeFile(
40
57
  envPath,
41
- `GENAI_API_KEY=\nTAVILY_API_KEY=\n`,
58
+ "GENAI_API_KEY=\nTAVILY_API_KEY=\n",
42
59
  "utf8",
43
60
  );
44
-
45
- console.log(`
46
- Igris configuration created.
47
-
48
- Configuration file:
49
- ${envPath}
50
-
51
- You will now be asked for your API keys.
52
- `);
53
61
  }
54
62
  }
55
63
 
56
64
  await ensureEnvFile();
57
65
 
58
- // Load environment variables from ~/.igris/.env
66
+ /* =========================================================
67
+ LOAD ENVIRONMENT VARIABLES
68
+ ========================================================= */
69
+
59
70
  config({
60
71
  path: envPath,
72
+ override: true,
61
73
  quiet: true,
62
74
  });
63
75
 
64
76
  /* =========================================================
65
- READ / SAVE API KEYS
77
+ API KEY SETUP
66
78
  ========================================================= */
67
79
 
68
80
  async function getApiKey(variableName, displayName) {
69
81
  let value = process.env[variableName]?.trim();
70
82
 
83
+ // Already configured
71
84
  if (value) {
72
85
  return value;
73
86
  }
74
87
 
75
- const key = await readline.question(
76
- `${displayName}: `,
77
- );
88
+ console.log("");
78
89
 
79
- value = key.trim();
90
+ value = (
91
+ await readline.question(`${displayName}: `)
92
+ ).trim();
80
93
 
81
94
  if (!value) {
82
- console.error(`\n${displayName} cannot be empty.`);
95
+ console.error(
96
+ `\n${displayName} cannot be empty.`,
97
+ );
98
+
99
+ readline.close();
83
100
  process.exit(1);
84
101
  }
85
102
 
86
- // Read existing .env
87
103
  let envContent = "";
88
104
 
89
105
  try {
90
- envContent = await readFile(envPath, "utf8");
106
+ envContent = await readFile(
107
+ envPath,
108
+ "utf8",
109
+ );
91
110
  } catch {
92
111
  envContent = "";
93
112
  }
94
113
 
95
- const regex = new RegExp(`^${variableName}=.*$`, "m");
114
+ const regex = new RegExp(
115
+ `^${variableName}=.*$`,
116
+ "m",
117
+ );
96
118
 
97
119
  if (regex.test(envContent)) {
98
120
  envContent = envContent.replace(
@@ -100,27 +122,23 @@ async function getApiKey(variableName, displayName) {
100
122
  `${variableName}=${value}`,
101
123
  );
102
124
  } else {
103
- envContent += `\n${variableName}=${value}\n`;
125
+ envContent += `\n${variableName}=${value}`;
104
126
  }
105
127
 
106
- await writeFile(envPath, envContent.trim() + "\n", "utf8");
128
+ await writeFile(
129
+ envPath,
130
+ envContent.trim() + "\n",
131
+ "utf8",
132
+ );
107
133
 
134
+ // Make it immediately available to process.env
108
135
  process.env[variableName] = value;
109
136
 
110
137
  return value;
111
138
  }
112
139
 
113
140
  /* =========================================================
114
- READLINE
115
- ========================================================= */
116
-
117
- const readline = rl.createInterface({
118
- input: process.stdin,
119
- output: process.stdout,
120
- });
121
-
122
- /* =========================================================
123
- API CONFIGURATION
141
+ GET REQUIRED API KEYS
124
142
  ========================================================= */
125
143
 
126
144
  const genaiApiKey = await getApiKey(
@@ -141,7 +159,11 @@ const tavly = tavily({
141
159
  apiKey: tavilyApiKey,
142
160
  });
143
161
 
144
- async function getLetestInfos({ query }) {
162
+ /* =========================================================
163
+ LATEST INFORMATION TOOL
164
+ ========================================================= */
165
+
166
+ async function getLatestInfos({ query }) {
145
167
  const response = await tavly.search(query, {
146
168
  searchDepth: "fast",
147
169
  maxResults: 3,
@@ -150,52 +172,70 @@ async function getLetestInfos({ query }) {
150
172
 
151
173
  const results = response.results;
152
174
 
153
- const content = results
175
+ return results
154
176
  .map((result) => result.content)
155
177
  .join("\n\n");
156
-
157
- return content;
158
178
  }
159
179
 
160
- const getLatestInfosTool = tool(getLetestInfos, {
161
- name: "get_latest_infos",
162
- description:
163
- "Get the latest updates from India, including news, policies, and stock market trends.",
164
- schema: z.object({
165
- query: z
166
- .string()
167
- .describe(
168
- "The query for which you want to get the latest updates from India.",
169
- ),
170
- }),
171
- });
180
+ const getLatestInfosTool = tool(
181
+ getLatestInfos,
182
+ {
183
+ name: "get_latest_infos",
184
+
185
+ description:
186
+ "Get the latest updates from India, including news, policies, and stock market trends.",
187
+
188
+ schema: z.object({
189
+ query: z
190
+ .string()
191
+ .describe(
192
+ "The query for which you want to get the latest updates from India.",
193
+ ),
194
+ }),
195
+ },
196
+ );
172
197
 
173
198
  /* =========================================================
174
- USER DATA
199
+ USER NAME
175
200
  ========================================================= */
176
201
 
177
202
  async function getUserName() {
178
203
  try {
179
204
  const userData = JSON.parse(
180
- await readFile(userDataPath, "utf8"),
205
+ await readFile(
206
+ userDataPath,
207
+ "utf8",
208
+ ),
181
209
  );
182
210
 
183
- return userData.name;
211
+ if (userData.name?.trim()) {
212
+ return userData.name.trim();
213
+ }
184
214
  } catch {
185
- const name = (
186
- await readline.question(
187
- "What is your name, My Lord? ",
188
- )
189
- ).trim();
215
+ // User data doesn't exist yet.
216
+ }
190
217
 
191
- await writeFile(
192
- userDataPath,
193
- JSON.stringify({ name }, null, 2),
194
- "utf8",
195
- );
218
+ const name = (
219
+ await readline.question(
220
+ "What is your name, My Lord? ",
221
+ )
222
+ ).trim();
196
223
 
197
- return name;
224
+ if (!name) {
225
+ return "My Lord";
198
226
  }
227
+
228
+ await writeFile(
229
+ userDataPath,
230
+ JSON.stringify(
231
+ { name },
232
+ null,
233
+ 2,
234
+ ),
235
+ "utf8",
236
+ );
237
+
238
+ return name;
199
239
  }
200
240
 
201
241
  const userName = await getUserName();
@@ -244,7 +284,7 @@ Use them only when needed for styling, context, or a poetic response.
244
284
  ];
245
285
 
246
286
  /* =========================================================
247
- START IGRIS
287
+ STARTUP MESSAGE
248
288
  ========================================================= */
249
289
 
250
290
  console.log(`
@@ -255,6 +295,7 @@ console.log(`
255
295
 
256
296
  Hi, My Lord, I am Igris, your loyal servant.
257
297
  How may I assist you today?
298
+
258
299
  `);
259
300
 
260
301
  /* =========================================================
@@ -263,21 +304,27 @@ How may I assist you today?
263
304
 
264
305
  try {
265
306
  while (true) {
266
- const prompt = await readline.question("You: ");
307
+ const prompt = await readline.question(
308
+ "You: ",
309
+ );
310
+
311
+ const trimmedPrompt = prompt.trim();
267
312
 
268
313
  if (
269
314
  ["exit", "quit"].includes(
270
- prompt.trim().toLowerCase(),
315
+ trimmedPrompt.toLowerCase(),
271
316
  )
272
317
  ) {
273
318
  break;
274
319
  }
275
320
 
276
- if (!prompt.trim()) {
321
+ if (!trimmedPrompt) {
277
322
  continue;
278
323
  }
279
324
 
280
- messages.push(new HumanMessage(prompt));
325
+ messages.push(
326
+ new HumanMessage(trimmedPrompt),
327
+ );
281
328
 
282
329
  const stream = await agent.stream(
283
330
  {
@@ -290,7 +337,7 @@ try {
290
337
 
291
338
  let aiResponse = "";
292
339
 
293
- console.log("Igris: ");
340
+ console.log("Igris:");
294
341
 
295
342
  for await (const [chunk] of stream) {
296
343
  if (chunk instanceof AIMessageChunk) {
@@ -299,11 +346,20 @@ try {
299
346
  }
300
347
  }
301
348
 
302
- messages.push(new AIMessage(aiResponse));
349
+ messages.push(
350
+ new AIMessage(aiResponse),
351
+ );
303
352
 
304
353
  process.stdout.write("\n\n");
305
354
  }
355
+ } catch (error) {
356
+ console.error(
357
+ "\nIgris encountered an error:",
358
+ );
359
+
360
+ console.error(
361
+ error?.message || error,
362
+ );
306
363
  } finally {
307
364
  readline.close();
308
- }
309
-
365
+ }
package/package.json CHANGED
@@ -1,20 +1,22 @@
1
1
  {
2
2
  "name": "igris-soul",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "A terminal AI assistant powered by Gemini and Tavily.",
5
5
  "license": "ISC",
6
6
  "author": "Barshan Majumdar",
7
7
  "type": "module",
8
8
  "main": "index.js",
9
9
  "bin": {
10
- "Arise": "index.js",
11
- "arise": "index.js",
12
- "igris-soul": "index.js",
13
- "igris": "index.js"
10
+ "Arise": "cli.cjs",
11
+ "arise": "cli.cjs",
12
+ "igris-soul": "cli.cjs",
13
+ "igris": "cli.cjs"
14
14
  },
15
15
  "files": [
16
- "index.js",
17
- ".env.example"
16
+ "index.js",
17
+ "cli.cjs",
18
+ ".env.example",
19
+ "README.md"
18
20
  ],
19
21
  "engines": {
20
22
  "node": ">=20"