localsage 1.0.1 → 1.0.4

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/README.md CHANGED
@@ -21,19 +21,23 @@ npm install -g localsage
21
21
 
22
22
  ## Quick Start
23
23
 
24
- 1. Create a `knowledge` folder in your working directory and add documents:
24
+ 1. Go to any folder that contains your documents (PDF, TXT, MD) — or create a `knowledge` subfolder for them:
25
25
 
26
26
  ```bash
27
- mkdir knowledge
28
- cp your-document.pdf knowledge/
27
+ cd my-documents
29
28
  ```
30
29
 
31
- 2. Start LocalSage:
30
+ 2. Start LocalSage right there:
32
31
 
33
32
  ```bash
34
33
  localsage
35
34
  ```
36
35
 
36
+ LocalSage picks the knowledge source automatically:
37
+ 1. the path you pass with `-k / --knowledge`, if given
38
+ 2. otherwise a `knowledge/` subfolder in the current directory, if it exists
39
+ 3. otherwise **the current directory itself** — so you can just `cd` into your docs folder and run `localsage`
40
+
37
41
  The first run downloads the model (~500 MB) to your Hugging Face cache. After that, it works fully offline.
38
42
 
39
43
  3. Ask questions about your documents!
@@ -50,7 +54,8 @@ The first run downloads the model (~500 MB) to your Hugging Face cache. After th
50
54
  localsage [options]
51
55
 
52
56
  -m, --model <model> Model to use (default: "onnx-community/Qwen2.5-0.5B-Instruct")
53
- -k, --knowledge <path> Knowledge folder path (default: "./knowledge")
57
+ -k, --knowledge <path> Knowledge folder path (default: ./knowledge if it
58
+ exists, else the current directory)
54
59
  --no-pdf Disable PDF support
55
60
  --max-tokens <number> Max tokens for response (default: 200)
56
61
  --temperature <number> Temperature for generation (default: 0.7)
@@ -98,7 +103,7 @@ main();
98
103
 
99
104
  ## How It Works
100
105
 
101
- 1. **Knowledge loading** — reads all TXT, MD, and PDF files from the knowledge folder (plus an optional `knowledge.txt` in the working directory)
106
+ 1. **Knowledge loading** — reads all TXT, MD, and PDF files from the resolved knowledge folder (explicit `-k` path → `./knowledge` subfolder → current directory), plus an optional `knowledge.txt` in the working directory
102
107
  2. **AI processing** — runs the Qwen model locally with `@huggingface/transformers`
103
108
  3. **History** — saves the conversation to `history.json` in the working directory
104
109
 
package/bin/localsage CHANGED
@@ -1,4 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // Wrapper that runs the LocalSage CLI
4
- require('../cli.js');
3
+ // LocalSage CLI entry point.
4
+ // cli.js exports runCLI and only auto-runs when it is the main module —
5
+ // which it is NOT when launched through this bin wrapper. So we must
6
+ // invoke it explicitly here.
7
+ const runCLI = require('../cli.js');
8
+
9
+ runCLI().catch((error) => {
10
+ console.error('Fatal Error:', error.message);
11
+ process.exit(1);
12
+ });
package/cli.js CHANGED
@@ -7,10 +7,13 @@ const LocalSage = require('./index');
7
7
 
8
8
  program
9
9
  .name('localsage')
10
- .version('1.0.0')
10
+ .version('1.0.3')
11
11
  .description('LocalSage — local, offline AI assistant with PDF & document knowledge')
12
12
  .option('-m, --model <model>', 'Model name', 'onnx-community/Qwen2.5-0.5B-Instruct')
13
- .option('-k, --knowledge <path>', 'Knowledge folder path', './knowledge')
13
+ .option(
14
+ '-k, --knowledge <path>',
15
+ 'Knowledge folder path (default: ./knowledge if it exists, else the current directory)'
16
+ )
14
17
  .option('--no-pdf', 'Disable PDF support')
15
18
  .option('--max-tokens <number>', 'Max tokens for response', '200')
16
19
  .option('--temperature <number>', 'Temperature for generation', '0.7')
@@ -33,7 +36,7 @@ async function runCLI() {
33
36
  console.log(chalk.green.bold(' 🧙 LocalSage — your local AI assistant '));
34
37
  console.log(chalk.blue('='.repeat(60)));
35
38
  console.log(chalk.gray(` Model: ${options.model}`));
36
- console.log(chalk.gray(` Knowledge: ${options.knowledge}`));
39
+ console.log(chalk.gray(` Knowledge: ${assistant.getResolvedKnowledgePath()}`));
37
40
  console.log(chalk.gray(` PDF Support: ${options.pdf !== false ? 'Enabled' : 'Disabled'}`));
38
41
  console.log(chalk.blue('='.repeat(60)));
39
42
  console.log(chalk.gray(' Commands:'));
package/index.js CHANGED
@@ -6,7 +6,9 @@ class LocalSage {
6
6
  constructor(options = {}) {
7
7
  this.options = {
8
8
  model: options.model || 'onnx-community/Qwen2.5-0.5B-Instruct',
9
- knowledgePath: options.knowledgePath || './knowledge',
9
+ // null = auto-resolve: ./knowledge if it exists, else the current
10
+ // working directory the user ran the command from
11
+ knowledgePath: options.knowledgePath || null,
10
12
  maxTokens: options.maxTokens || 200,
11
13
  temperature: options.temperature || 0.7,
12
14
  topP: options.topP || 0.9,
@@ -85,6 +87,10 @@ ${knowledge}`;
85
87
  return this.knowledge.getKnowledge();
86
88
  }
87
89
 
90
+ getResolvedKnowledgePath() {
91
+ return this.knowledge.getResolvedPath();
92
+ }
93
+
88
94
  async reloadKnowledge() {
89
95
  await this.knowledge.loadKnowledge();
90
96
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "localsage",
3
- "version": "1.0.1",
3
+ "version": "1.0.4",
4
4
  "description": "LocalSage — a local, offline AI assistant with PDF & document knowledge. Powered by Qwen via Transformers.js. Pure Node.js, no Python.",
5
5
  "main": "index.js",
6
6
  "bin": {
package/src/knowledge.js CHANGED
@@ -2,17 +2,41 @@ const fs = require('fs-extra');
2
2
  const path = require('path');
3
3
 
4
4
  class KnowledgeManager {
5
- constructor(knowledgePath = './knowledge') {
6
- this.knowledgePath = knowledgePath;
5
+ /**
6
+ * @param {string|null} knowledgePath - explicit path from the user, or null
7
+ * to auto-resolve: use ./knowledge if it exists, otherwise the
8
+ * current working directory itself.
9
+ */
10
+ constructor(knowledgePath = null) {
11
+ this.requestedPath = knowledgePath;
12
+ this.knowledgePath = null; // resolved in loadKnowledge()
7
13
  this.knowledge = '';
8
14
  }
9
15
 
16
+ resolvePath() {
17
+ if (this.requestedPath) {
18
+ return path.resolve(this.requestedPath);
19
+ }
20
+ const sub = path.join(process.cwd(), 'knowledge');
21
+ if (fs.pathExistsSync(sub)) {
22
+ return sub;
23
+ }
24
+ // Fall back to the directory the user ran the command from
25
+ return process.cwd();
26
+ }
27
+
10
28
  async loadKnowledge() {
29
+ this.knowledgePath = this.resolvePath();
11
30
  let combinedKnowledge = '';
12
31
 
13
- // Load from knowledge.txt in the current directory (optional)
32
+ // Load a standalone knowledge.txt from the working directory
33
+ // but only if it isn't already inside the folder we're about to scan
34
+ // (avoids loading it twice when knowledgePath === cwd).
14
35
  const txtFile = path.join(process.cwd(), 'knowledge.txt');
15
- if (await fs.pathExists(txtFile)) {
36
+ if (
37
+ path.dirname(txtFile) !== this.knowledgePath &&
38
+ (await fs.pathExists(txtFile))
39
+ ) {
16
40
  try {
17
41
  combinedKnowledge += await fs.readFile(txtFile, 'utf-8');
18
42
  console.log('Knowledge loaded from knowledge.txt');
@@ -21,9 +45,8 @@ class KnowledgeManager {
21
45
  }
22
46
  }
23
47
 
24
- // Load from the knowledge folder
25
48
  if (await fs.pathExists(this.knowledgePath)) {
26
- console.log(`Loading knowledge from folder: ${this.knowledgePath}/`);
49
+ console.log(`Loading knowledge from: ${this.knowledgePath}`);
27
50
  const folderKnowledge = await this.loadFolder(this.knowledgePath);
28
51
  if (folderKnowledge) {
29
52
  if (combinedKnowledge) {
@@ -31,6 +54,8 @@ class KnowledgeManager {
31
54
  }
32
55
  combinedKnowledge += folderKnowledge;
33
56
  }
57
+ } else {
58
+ console.log(`Knowledge path not found: ${this.knowledgePath}`);
34
59
  }
35
60
 
36
61
  this.knowledge = combinedKnowledge;
@@ -50,7 +75,13 @@ class KnowledgeManager {
50
75
 
51
76
  for (const file of files) {
52
77
  const filePath = path.join(folderPath, file);
53
- const stat = await fs.stat(filePath);
78
+
79
+ let stat;
80
+ try {
81
+ stat = await fs.stat(filePath);
82
+ } catch {
83
+ continue;
84
+ }
54
85
  if (stat.isDirectory()) continue;
55
86
 
56
87
  const ext = path.extname(file).toLowerCase();
@@ -80,7 +111,6 @@ class KnowledgeManager {
80
111
 
81
112
  async extractPDF(filePath) {
82
113
  try {
83
- // Lazy-require so the app still starts if pdf-parse is missing/broken
84
114
  const pdfParse = require('pdf-parse');
85
115
  const dataBuffer = await fs.readFile(filePath);
86
116
  const data = await pdfParse(dataBuffer);
@@ -91,6 +121,10 @@ class KnowledgeManager {
91
121
  }
92
122
  }
93
123
 
124
+ getResolvedPath() {
125
+ return this.knowledgePath || this.resolvePath();
126
+ }
127
+
94
128
  getKnowledge(truncate = true, maxLength = 4000) {
95
129
  if (!truncate || this.knowledge.length <= maxLength) {
96
130
  return this.knowledge;