igris-soul 1.0.0 → 1.1.0

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/README.md +345 -31
  2. package/index.js +200 -36
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # Igris Soul
2
2
 
3
- > To use Igris Soul from anywhere, install it globally and run `Arise`:
3
+ > Your personal AI assistant in the terminal, inspired by Igris from Solo Leveling.
4
+ >
5
+ > Install it globally and summon Igris from anywhere:
4
6
  >
5
7
  > ```bash
6
8
  > npm install -g igris-soul
@@ -9,33 +11,231 @@
9
11
 
10
12
  ## Disclaimer
11
13
 
12
- This project sends your prompts to Google Gemini and may send web-search requests to Tavily. Do not enter passwords, private keys, personal data, confidential business information, or any other sensitive information into the assistant. You are responsible for reviewing generated answers and for the API usage and charges associated with your keys. This project is provided for educational and personal use without guarantees about accuracy, availability, or fitness for a particular purpose.
14
+ This project sends your prompts to Google Gemini and may send web-search requests to Tavily. Do not enter passwords, private keys, personal data, confidential business information, or any other sensitive information into the assistant.
15
+
16
+ You are responsible for reviewing generated answers and for the API usage and charges associated with your API keys.
17
+
18
+ This project is provided for educational and personal use without guarantees about accuracy, availability, or fitness for a particular purpose.
19
+
20
+ ---
13
21
 
14
22
  ## Requirements
15
23
 
16
- - Node.js 20 or newer
17
- - A Google Gemini API key
18
- - A Tavily API key for current-information searches
24
+ Before installing Igris Soul, make sure you have:
25
+
26
+ * Node.js 20 or newer
27
+ * A Google Gemini API key
28
+ * A Tavily API key for current-information searches
29
+ * An npm account if you want to install or publish the package through npm
30
+
31
+ ---
32
+
33
+ # Installation
34
+
35
+ ## Install Globally
36
+
37
+ To use Igris Soul from anywhere on your computer:
38
+
39
+ ```bash
40
+ npm install -g igris-soul
41
+ ```
42
+
43
+ After installation, start Igris with:
44
+
45
+ ```bash
46
+ Arise
47
+ ```
48
+
49
+ You can run `Arise` from any directory.
50
+
51
+ For example:
52
+
53
+ ```bash
54
+ C:\Users\YourName> Arise
55
+ ```
56
+
57
+ or:
58
+
59
+ ```bash
60
+ D:\Projects\MyProject> Arise
61
+ ```
62
+
63
+ or:
64
+
65
+ ```bash
66
+ E:\Anything> Arise
67
+ ```
68
+
69
+ Igris does not depend on the directory from which you launch it.
70
+
71
+ ---
72
+
73
+ # First-Time Setup
74
+
75
+ On the first launch, Igris automatically creates its configuration directory in your home directory:
76
+
77
+ ```text
78
+ ~/.igris/
79
+ ```
80
+
81
+ It will automatically create:
82
+
83
+ ```text
84
+ ~/.igris/
85
+ ├── .env
86
+ └── user.json
87
+ ```
88
+
89
+ You do **not** need to manually create these files.
90
+
91
+ Igris will ask for the required API keys if they are not already configured:
92
+
93
+ ```text
94
+ Enter your Gemini API key:
95
+ Enter your Tavily API key:
96
+ ```
97
+
98
+ The keys are then stored locally in:
19
99
 
20
- ## Configuration
100
+ ```text
101
+ ~/.igris/.env
102
+ ```
103
+
104
+ On Windows, this is typically:
105
+
106
+ ```text
107
+ C:\Users\YourName\.igris\.env
108
+ ```
109
+
110
+ Your name is stored separately in:
111
+
112
+ ```text
113
+ C:\Users\YourName\.igris\user.json
114
+ ```
115
+
116
+ On future launches, Igris automatically loads the saved configuration.
117
+
118
+ ---
21
119
 
22
- For a local checkout, create a `.env` file in the project root. For a global installation, create `.igris/.env` in your home directory. Use `.env.example` as the template:
120
+ # Configuration
121
+
122
+ The automatically created `.env` file contains:
23
123
 
24
124
  ```env
25
125
  GENAI_API_KEY=your_google_gemini_api_key
26
126
  TAVILY_API_KEY=your_tavily_api_key
27
127
  ```
28
128
 
29
- Never commit `.env` or share its contents. It is already excluded by `.gitignore`.
129
+ ### Environment Variables
130
+
131
+ | Variable | Purpose |
132
+ | ---------------- | ------------------------------- |
133
+ | `GENAI_API_KEY` | Google Gemini API key |
134
+ | `TAVILY_API_KEY` | Tavily API key for web searches |
135
+
136
+ You can manually edit:
137
+
138
+ ```text
139
+ ~/.igris/.env
140
+ ```
141
+
142
+ if you need to change your API keys.
143
+
144
+ ### Security
145
+
146
+ Never commit or share your `.env` file.
147
+
148
+ Never put real API keys inside the source code.
149
+
150
+ The `.env` file contains sensitive credentials and should remain on your local machine.
151
+
152
+ The project includes `.env.example` as a safe template:
153
+
154
+ ```env
155
+ GENAI_API_KEY=
156
+ TAVILY_API_KEY=
157
+ ```
158
+
159
+ ---
160
+
161
+ # Running Igris
162
+
163
+ After installation:
164
+
165
+ ```bash
166
+ Arise
167
+ ```
168
+
169
+ You will see:
170
+
171
+ ```text
172
+ +----------------------+
173
+ | IGRIS |
174
+ | AGENT ONLINE |
175
+ +----------------------+
176
+
177
+ Hi, My Lord, I am Igris, your loyal servant.
178
+ How may I assist you today?
179
+ ```
180
+
181
+ You can then enter your prompts directly in the terminal.
182
+
183
+ Example:
184
+
185
+ ```text
186
+ You: Explain binary search in Java
187
+
188
+ Igris:
189
+ ...
190
+ ```
191
+
192
+ To exit the assistant:
193
+
194
+ ```text
195
+ exit
196
+ ```
197
+
198
+ or:
199
+
200
+ ```text
201
+ quit
202
+ ```
203
+
204
+ ---
205
+
206
+ # Features
207
+
208
+ * Interactive terminal-based AI assistant
209
+ * Google Gemini-powered responses
210
+ * Streaming AI responses
211
+ * Tavily web search for current information
212
+ * Conversation history during the current session
213
+ * Personalized responses using your saved name
214
+ * First-launch name setup
215
+ * Persistent configuration
216
+ * Automatic `.igris` directory creation
217
+ * Automatic `.env` creation
218
+ * Global CLI access
219
+ * Runs from any directory
220
+ * `Arise` command for launching the assistant
30
221
 
31
- When both files exist, the `.env` file in the current directory takes priority over the home-directory configuration.
222
+ ---
32
223
 
33
- ## Run From This Repository
224
+ # Local Development
34
225
 
35
- Install dependencies and start Igris Soul:
226
+ If you want to run Igris Soul directly from the source code instead of installing it globally:
227
+
228
+ Clone or download the repository and enter the project directory.
229
+
230
+ Install dependencies:
36
231
 
37
232
  ```bash
38
233
  npm install
234
+ ```
235
+
236
+ Then start Igris:
237
+
238
+ ```bash
39
239
  npm start
40
240
  ```
41
241
 
@@ -45,50 +245,164 @@ You can also run:
45
245
  node index.js
46
246
  ```
47
247
 
48
- Type `exit` or `quit` to close the assistant.
248
+ The same `~/.igris/.env` configuration is used.
249
+
250
+ ---
251
+
252
+ # Test the Package Locally
253
+
254
+ Before publishing a new version to npm, you can test the CLI directly from the package directory.
49
255
 
50
- ## Install As A Global CLI
256
+ Install the local package globally:
51
257
 
52
258
  ```bash
53
- npm install -g igris-soul
54
- Arise
259
+ npm install -g .
55
260
  ```
56
261
 
57
- The `arise`, `igris-soul`, and `igris` aliases are also available. To test the package locally before publishing:
262
+ Then run:
58
263
 
59
264
  ```bash
60
- npm install -g .
61
265
  Arise
62
266
  ```
63
267
 
64
- ## Features
268
+ This allows you to test the global CLI behavior using your local source code.
269
+
270
+ ---
271
+
272
+ # Updating Igris Soul
65
273
 
66
- - Interactive terminal conversation with streamed responses
67
- - Google Gemini-powered answers
68
- - Tavily web search for current information
69
- - Conversation history during the current session
70
- - First-launch name setup
71
- - Persistent name storage in `~/.igris/user.json`
72
- - `Arise`, `igris-soul`, and `igris` global commands
274
+ When a new version is published, update your global installation with:
73
275
 
74
- The saved name belongs to the local operating-system user account and remains available across directories and package updates until the file is deleted.
276
+ ```bash
277
+ npm install -g igris-soul@latest
278
+ ```
279
+
280
+ Your personal configuration remains outside the npm package:
281
+
282
+ ```text
283
+ ~/.igris/
284
+ ├── .env
285
+ └── user.json
286
+ ```
75
287
 
76
- ## Publishing
288
+ Therefore, updating the package does not require you to enter your API keys again.
77
289
 
78
- Log in to npm and publish the package:
290
+ ---
291
+
292
+ # Uninstalling
293
+
294
+ To remove the globally installed package:
295
+
296
+ ```bash
297
+ npm uninstall -g igris-soul
298
+ ```
299
+
300
+ Your personal Igris configuration remains in:
301
+
302
+ ```text
303
+ ~/.igris/
304
+ ```
305
+
306
+ If you want to completely remove your local Igris configuration as well, delete the `.igris` directory manually.
307
+
308
+ ---
309
+
310
+ # Publishing to npm
311
+
312
+ If you are the package maintainer and want to publish a new version:
313
+
314
+ First, log in to npm:
79
315
 
80
316
  ```bash
81
317
  npm login
318
+ ```
319
+
320
+ Verify your account:
321
+
322
+ ```bash
323
+ npm whoami
324
+ ```
325
+
326
+ Run the tests:
327
+
328
+ ```bash
82
329
  npm test
83
- npm publish
84
330
  ```
85
331
 
86
- To verify the package without uploading it:
332
+ Check what will be included in the package:
87
333
 
88
334
  ```bash
89
335
  npm publish --dry-run
90
336
  ```
91
337
 
92
- ## License
338
+ If everything looks correct, publish:
339
+
340
+ ```bash
341
+ npm publish
342
+ ```
343
+
344
+ ## Updating the Version
345
+
346
+ npm does not allow publishing the same package version twice.
347
+
348
+ For a bug fix:
349
+
350
+ ```bash
351
+ npm version patch
352
+ ```
353
+
354
+ For a new feature:
355
+
356
+ ```bash
357
+ npm version minor
358
+ ```
359
+
360
+ For a breaking change:
361
+
362
+ ```bash
363
+ npm version major
364
+ ```
365
+
366
+ Then publish the new version:
367
+
368
+ ```bash
369
+ npm publish
370
+ ```
371
+
372
+ Example:
373
+
374
+ ```text
375
+ 1.0.0 → 1.0.1
376
+ ```
377
+
378
+ ---
379
+
380
+ # Package Usage
381
+
382
+ Igris Soul is primarily designed as a global terminal assistant.
383
+
384
+ Install:
385
+
386
+ ```bash
387
+ npm install -g igris-soul
388
+ ```
389
+
390
+ Run:
391
+
392
+ ```bash
393
+ Arise
394
+ ```
395
+
396
+ The package is installed globally, while user-specific configuration is stored separately in:
397
+
398
+ ```text
399
+ ~/.igris/
400
+ ```
401
+
402
+ This allows Igris to maintain its configuration independently of the directory from which it is launched.
403
+
404
+ ---
405
+
406
+ # License
93
407
 
94
408
  ISC
package/index.js CHANGED
@@ -1,9 +1,12 @@
1
- #!/usr/bin/env node
2
-
3
1
  import { ChatGoogleGenerativeAI } from "@langchain/google-genai";
4
2
  import { config } from "dotenv";
5
3
  import { tavily } from "@tavily/core";
6
- import { mkdir, readFile, writeFile } from "node:fs/promises";
4
+ import {
5
+ mkdir,
6
+ readFile,
7
+ writeFile,
8
+ access,
9
+ } from "node:fs/promises";
7
10
  import { homedir } from "node:os";
8
11
  import { join } from "node:path";
9
12
  import rl from "readline/promises";
@@ -17,15 +20,128 @@ import {
17
20
  } from "langchain";
18
21
  import * as z from "zod";
19
22
 
23
+ /* =========================================================
24
+ IGRIS CONFIGURATION
25
+ ========================================================= */
26
+
27
+ const igrisDir = join(homedir(), ".igris");
28
+ const envPath = join(igrisDir, ".env");
29
+ const userDataPath = join(igrisDir, "user.json");
30
+
31
+ // Create ~/.igris automatically
32
+ await mkdir(igrisDir, { recursive: true });
33
+
34
+ // Check whether .env exists
35
+ async function ensureEnvFile() {
36
+ try {
37
+ await access(envPath);
38
+ } catch {
39
+ await writeFile(
40
+ envPath,
41
+ `GENAI_API_KEY=\nTAVILY_API_KEY=\n`,
42
+ "utf8",
43
+ );
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
+ }
54
+ }
55
+
56
+ await ensureEnvFile();
57
+
58
+ // Load environment variables from ~/.igris/.env
20
59
  config({
21
- path: [join(process.cwd(), ".env"), join(homedir(), ".igris", ".env")],
60
+ path: envPath,
22
61
  quiet: true,
23
62
  });
63
+
64
+ /* =========================================================
65
+ READ / SAVE API KEYS
66
+ ========================================================= */
67
+
68
+ async function getApiKey(variableName, displayName) {
69
+ let value = process.env[variableName]?.trim();
70
+
71
+ if (value) {
72
+ return value;
73
+ }
74
+
75
+ const key = await readline.question(
76
+ `${displayName}: `,
77
+ );
78
+
79
+ value = key.trim();
80
+
81
+ if (!value) {
82
+ console.error(`\n${displayName} cannot be empty.`);
83
+ process.exit(1);
84
+ }
85
+
86
+ // Read existing .env
87
+ let envContent = "";
88
+
89
+ try {
90
+ envContent = await readFile(envPath, "utf8");
91
+ } catch {
92
+ envContent = "";
93
+ }
94
+
95
+ const regex = new RegExp(`^${variableName}=.*$`, "m");
96
+
97
+ if (regex.test(envContent)) {
98
+ envContent = envContent.replace(
99
+ regex,
100
+ `${variableName}=${value}`,
101
+ );
102
+ } else {
103
+ envContent += `\n${variableName}=${value}\n`;
104
+ }
105
+
106
+ await writeFile(envPath, envContent.trim() + "\n", "utf8");
107
+
108
+ process.env[variableName] = value;
109
+
110
+ return value;
111
+ }
112
+
113
+ /* =========================================================
114
+ READLINE
115
+ ========================================================= */
116
+
117
+ const readline = rl.createInterface({
118
+ input: process.stdin,
119
+ output: process.stdout,
120
+ });
121
+
122
+ /* =========================================================
123
+ API CONFIGURATION
124
+ ========================================================= */
125
+
126
+ const genaiApiKey = await getApiKey(
127
+ "GENAI_API_KEY",
128
+ "Enter your Gemini API key",
129
+ );
130
+
131
+ const tavilyApiKey = await getApiKey(
132
+ "TAVILY_API_KEY",
133
+ "Enter your Tavily API key",
134
+ );
135
+
136
+ /* =========================================================
137
+ TAVILY
138
+ ========================================================= */
139
+
24
140
  const tavly = tavily({
25
- apiKey: process.env.TAVILY_API_KEY,
141
+ apiKey: tavilyApiKey,
26
142
  });
143
+
27
144
  async function getLetestInfos({ query }) {
28
- // return "India's latest updates as per today are : New kolkata metro staion inaugurated, New AI policy released by the government, and the stock market is showing positive trends.";
29
145
  const response = await tavly.search(query, {
30
146
  searchDepth: "fast",
31
147
  maxResults: 3,
@@ -33,8 +149,11 @@ async function getLetestInfos({ query }) {
33
149
  });
34
150
 
35
151
  const results = response.results;
36
- const content = results.map((result) => result.content).join("\n\n");
37
- // console.log("Content:", content);
152
+
153
+ const content = results
154
+ .map((result) => result.content)
155
+ .join("\n\n");
156
+
38
157
  return content;
39
158
  }
40
159
 
@@ -51,76 +170,115 @@ const getLatestInfosTool = tool(getLetestInfos, {
51
170
  }),
52
171
  });
53
172
 
54
- const readline = rl.createInterface({
55
- input: process.stdin,
56
- output: process.stdout,
57
- });
58
-
59
- const userDataPath = join(homedir(), ".igris", "user.json");
173
+ /* =========================================================
174
+ USER DATA
175
+ ========================================================= */
60
176
 
61
177
  async function getUserName() {
62
178
  try {
63
- const userData = JSON.parse(await readFile(userDataPath, "utf8"));
179
+ const userData = JSON.parse(
180
+ await readFile(userDataPath, "utf8"),
181
+ );
182
+
64
183
  return userData.name;
65
184
  } catch {
66
185
  const name = (
67
- await readline.question("What is your name, My Lord? ")
186
+ await readline.question(
187
+ "What is your name, My Lord? ",
188
+ )
68
189
  ).trim();
69
- await mkdir(join(homedir(), ".igris"), { recursive: true });
70
- await writeFile(userDataPath, JSON.stringify({ name }, null, 2));
190
+
191
+ await writeFile(
192
+ userDataPath,
193
+ JSON.stringify({ name }, null, 2),
194
+ "utf8",
195
+ );
196
+
71
197
  return name;
72
198
  }
73
199
  }
74
200
 
75
201
  const userName = await getUserName();
76
202
 
203
+ /* =========================================================
204
+ GEMINI MODEL
205
+ ========================================================= */
206
+
77
207
  const model = new ChatGoogleGenerativeAI({
78
208
  model: "gemini-3.5-flash-lite",
79
- apiKey: process.env.GENAI_API_KEY,
209
+ apiKey: genaiApiKey,
80
210
  });
81
211
 
212
+ /* =========================================================
213
+ AGENT
214
+ ========================================================= */
215
+
82
216
  const agent = createAgent({
83
217
  model,
84
218
  tools: [getLatestInfosTool],
85
219
  });
86
220
 
87
- // const response = await model.stream("Write a code in Java that actually shows abstraction, encapsulation, inheritance and polymorphism in a single program.go");
88
- // for await (const chunk of response) {
89
- // process.stdout.write(chunk.text);
90
- // }
91
-
92
- // const prompt = await readline.question("Enter your prompt: ");
93
- // console.log("Prompt:", prompt);
94
- // readline.close();
221
+ /* =========================================================
222
+ SYSTEM MESSAGE
223
+ ========================================================= */
95
224
 
96
225
  const messages = [
97
226
  new SystemMessage(`
98
- You are Igris from Solo leveling, and the user's name is ${userName}. Always address the user as My Lord. You are a Senior Software Developer and also an ML engineer, and your task is to answer queries as per the solo levelling style. Dont give unnecessary long responses, give responses to the point, and use plain text without markdown.
99
- Today is ${new Date().toLocaleDateString()} and the time is ${new Date().toLocaleTimeString()}.
100
- Dont use always te date and time, but for styling or poetic respsonse you can use it, but dont use it in every response, use it only when needed.
101
- `),
227
+ You are Igris from Solo Leveling, and the user's name is ${userName}.
228
+
229
+ Always address the user as My Lord.
230
+
231
+ You are a Senior Software Developer and ML Engineer.
232
+
233
+ Your task is to answer queries in the Solo Leveling style.
234
+
235
+ Do not give unnecessarily long responses.
236
+ Give responses to the point.
237
+ Use plain text without markdown.
238
+
239
+ Today is ${new Date().toLocaleDateString()} and the time is ${new Date().toLocaleTimeString()}.
240
+
241
+ Do not always mention the date and time.
242
+ Use them only when needed for styling, context, or a poetic response.
243
+ `),
102
244
  ];
103
245
 
246
+ /* =========================================================
247
+ START IGRIS
248
+ ========================================================= */
249
+
104
250
  console.log(`
105
251
  +----------------------+
106
252
  | IGRIS |
107
253
  | AGENT ONLINE |
108
254
  +----------------------+
109
255
 
110
- Hi, My Lord, I am Igris, your loyal servant. How may I assist you today?\n
256
+ Hi, My Lord, I am Igris, your loyal servant.
257
+ How may I assist you today?
111
258
  `);
112
259
 
113
- // console.log(
114
- // "Hi, My Lord, I am Igris, your loyal servant. How may I assist you today?\n",
115
- // );
260
+ /* =========================================================
261
+ CHAT LOOP
262
+ ========================================================= */
263
+
116
264
  try {
117
265
  while (true) {
118
266
  const prompt = await readline.question("You: ");
119
- if (["exit", "quit"].includes(prompt.trim().toLowerCase())) {
267
+
268
+ if (
269
+ ["exit", "quit"].includes(
270
+ prompt.trim().toLowerCase(),
271
+ )
272
+ ) {
120
273
  break;
121
274
  }
122
275
 
276
+ if (!prompt.trim()) {
277
+ continue;
278
+ }
279
+
123
280
  messages.push(new HumanMessage(prompt));
281
+
124
282
  const stream = await agent.stream(
125
283
  {
126
284
  messages,
@@ -129,17 +287,23 @@ try {
129
287
  streamMode: "messages",
130
288
  },
131
289
  );
290
+
132
291
  let aiResponse = "";
292
+
133
293
  console.log("Igris: ");
294
+
134
295
  for await (const [chunk] of stream) {
135
296
  if (chunk instanceof AIMessageChunk) {
136
297
  process.stdout.write(chunk.text);
137
298
  aiResponse += chunk.text;
138
299
  }
139
300
  }
301
+
140
302
  messages.push(new AIMessage(aiResponse));
303
+
141
304
  process.stdout.write("\n\n");
142
305
  }
143
306
  } finally {
144
307
  readline.close();
145
308
  }
309
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "igris-soul",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "A terminal AI assistant powered by Gemini and Tavily.",
5
5
  "license": "ISC",
6
6
  "author": "Barshan Majumdar",