gnosys 4.2.0 → 4.3.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.
package/README.md CHANGED
@@ -81,7 +81,7 @@ gnosys add "We chose PostgreSQL over MySQL for its JSON support and mature ecosy
81
81
  gnosys recall "database selection"
82
82
  ```
83
83
 
84
- > **Multi-machine?** Set `GNOSYS_GLOBAL` to a cloud-synced folder (iCloud Drive, Dropbox, OneDrive) and both machines share the same brain. See the [User Guide — Installation & Setup](https://gnosys.ai/guide.html#guide-installation) for the full walkthrough, memory scopes, and multi-machine setup.
84
+ > **Multi-machine?** Set `GNOSYS_GLOBAL` to a cloud-synced folder (iCloud Drive, Dropbox, OneDrive) and both machines share the same brain. After updating, run `gnosys upgrade` — it re-syncs all projects, regenerates agent rules, and warns other machines to upgrade too. See the [User Guide — Installation & Setup](https://gnosys.ai/guide.html#guide-installation) for the full walkthrough, memory scopes, and multi-machine setup.
85
85
 
86
86
  ### Agent / Helper Library
87
87
 
@@ -245,8 +245,7 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
245
245
  "mcpServers": {
246
246
  "gnosys": {
247
247
  "command": "gnosys",
248
- "args": ["serve"],
249
- "env": { "ANTHROPIC_API_KEY": "your-key-here" }
248
+ "args": ["serve"]
250
249
  }
251
250
  }
252
251
  }
@@ -261,8 +260,7 @@ Add to `.cursor/mcp.json`:
261
260
  "mcpServers": {
262
261
  "gnosys": {
263
262
  "command": "gnosys",
264
- "args": ["serve"],
265
- "env": { "ANTHROPIC_API_KEY": "your-key-here" }
263
+ "args": ["serve"]
266
264
  }
267
265
  }
268
266
  }
@@ -282,11 +280,10 @@ Add to `.codex/config.toml`:
282
280
  [mcp.gnosys]
283
281
  type = "local"
284
282
  command = ["gnosys", "serve"]
285
-
286
- [mcp.gnosys.env]
287
- ANTHROPIC_API_KEY = "your-key-here"
288
283
  ```
289
284
 
285
+ > **Note:** API keys are configured globally in `~/.config/gnosys/.env`, not per-IDE. See [LLM Provider Setup](https://gnosys.ai/guide.html#guide-installation) in the User Guide.
286
+
290
287
  ---
291
288
 
292
289
  ## MCP Tools
@@ -352,7 +349,7 @@ All memories live in a single `~/.gnosys/gnosys.db` with `project_id` and `scope
352
349
 
353
350
  ### LLM Providers
354
351
 
355
- Five providers behind a single interface — switch between cloud and local with one command:
352
+ Eight providers behind a single interface — switch between cloud and local with one command:
356
353
 
357
354
  | Provider | Type | Default Model | API Key Env Var |
358
355
  |----------|------|---------------|-----------------|
@@ -361,6 +358,9 @@ Five providers behind a single interface — switch between cloud and local with
361
358
  | **Groq** | Cloud | llama-3.3-70b-versatile | `GROQ_API_KEY` |
362
359
  | **OpenAI** | Cloud | gpt-4o-mini | `OPENAI_API_KEY` |
363
360
  | **LM Studio** | Local | default | — (runs locally) |
361
+ | **xAI** | Cloud | grok-2 | `XAI_API_KEY` |
362
+ | **Mistral** | Cloud | mistral-large-latest | `MISTRAL_API_KEY` |
363
+ | **Custom** | Any | (user-defined) | `GNOSYS_LLM_API_KEY` |
364
364
 
365
365
  Route tasks to different providers — a cheap model for structuring, a powerful model for synthesis:
366
366
 
@@ -455,7 +455,7 @@ All commands support `--json` for programmatic output. See the [User Guide](http
455
455
 
456
456
  **History:** `history`, `rollback`
457
457
 
458
- **Maintenance:** `maintain`, `dearchive`, `dream`, `reindex`, `reindex-graph`
458
+ **Maintenance:** `maintain`, `dearchive`, `dream`, `reindex`, `reindex-graph`, `upgrade`
459
459
 
460
460
  **Export & config:** `export`, `config show`, `config set`, `dashboard`, `doctor`, `stores`
461
461
 
@@ -505,7 +505,7 @@ src/
505
505
  embeddings.ts # Lazy semantic embeddings (all-MiniLM-L6-v2)
506
506
  hybridSearch.ts # Hybrid search with RRF fusion
507
507
  ask.ts # Freeform Q&A with LLM synthesis + citations
508
- llm.ts # LLM abstraction (5 providers)
508
+ llm.ts # LLM abstraction (8 providers)
509
509
  maintenance.ts # Auto-maintenance: decay, dedup, archiving
510
510
  archive.ts # Two-tier memory: active <-> archive
511
511
  recall.ts # Ultra-fast recall for agent orchestrators
package/dist/cli.js CHANGED
@@ -20,7 +20,7 @@ import { groupByPeriod, computeStats } from "./lib/timeline.js";
20
20
  import { buildLinkGraph, getBacklinks, getOutgoingLinks, formatGraphSummary } from "./lib/wikilinks.js";
21
21
  import { bootstrap, discoverFiles } from "./lib/bootstrap.js";
22
22
  import { performImport, formatImportSummary } from "./lib/import.js";
23
- import { loadConfig, generateConfigTemplate, DEFAULT_CONFIG, writeConfig, resolveTaskModel, ALL_PROVIDERS } from "./lib/config.js";
23
+ import { loadConfig, generateConfigTemplate, DEFAULT_CONFIG, writeConfig, resolveTaskModel, ALL_PROVIDERS, getProviderModel } from "./lib/config.js";
24
24
  import { GnosysEmbeddings } from "./lib/embeddings.js";
25
25
  import { GnosysHybridSearch } from "./lib/hybridSearch.js";
26
26
  import { GnosysAsk } from "./lib/ask.js";
@@ -429,6 +429,24 @@ program
429
429
  await fs.writeFile(path.join(storePath, ".config", "gnosys-config.json"), generateConfigTemplate() + "\n", "utf-8");
430
430
  const changelog = `# Gnosys Changelog\n\n## ${new Date().toISOString().split("T")[0]}\n\n- Store initialized\n`;
431
431
  await fs.writeFile(path.join(storePath, "CHANGELOG.md"), changelog, "utf-8");
432
+ // Add .gnosys/ to project's .gitignore if in a git repo
433
+ try {
434
+ const projectGitignore = path.join(targetDir, ".gitignore");
435
+ let gitignoreContent = "";
436
+ try {
437
+ gitignoreContent = await fs.readFile(projectGitignore, "utf-8");
438
+ }
439
+ catch {
440
+ // No .gitignore yet
441
+ }
442
+ if (!gitignoreContent.includes(".gnosys")) {
443
+ const entry = "\n# Gnosys memory store\n.gnosys/\n";
444
+ await fs.writeFile(projectGitignore, gitignoreContent + entry, "utf-8");
445
+ }
446
+ }
447
+ catch {
448
+ // Non-critical
449
+ }
432
450
  try {
433
451
  const { execSync } = await import("child_process");
434
452
  execSync("git init", { cwd: storePath, stdio: "pipe" });
@@ -1642,6 +1660,16 @@ configCmd
1642
1660
  cfg.llm.openai.model = value;
1643
1661
  else if (p === "lmstudio")
1644
1662
  cfg.llm.lmstudio.model = value;
1663
+ else if (p === "xai")
1664
+ cfg.llm.xai.model = value;
1665
+ else if (p === "mistral")
1666
+ cfg.llm.mistral.model = value;
1667
+ else if (p === "custom") {
1668
+ if (!cfg.llm.custom)
1669
+ cfg.llm.custom = { model: value, baseUrl: "" };
1670
+ else
1671
+ cfg.llm.custom.model = value;
1672
+ }
1645
1673
  console.log(`Model set to: ${value} (for ${p})`);
1646
1674
  break;
1647
1675
  }
@@ -1700,6 +1728,35 @@ configCmd
1700
1728
  cfg.llm.lmstudio.model = value;
1701
1729
  console.log(`LM Studio model set to: ${value}`);
1702
1730
  break;
1731
+ case "xai-model":
1732
+ cfg.llm.xai.model = value;
1733
+ console.log(`xAI model set to: ${value}`);
1734
+ break;
1735
+ case "mistral-model":
1736
+ cfg.llm.mistral.model = value;
1737
+ console.log(`Mistral model set to: ${value}`);
1738
+ break;
1739
+ case "custom-url":
1740
+ if (!cfg.llm.custom)
1741
+ cfg.llm.custom = { model: "", baseUrl: value };
1742
+ else
1743
+ cfg.llm.custom.baseUrl = value;
1744
+ console.log(`Custom provider base URL set to: ${value}`);
1745
+ break;
1746
+ case "custom-model":
1747
+ if (!cfg.llm.custom)
1748
+ cfg.llm.custom = { model: value, baseUrl: "" };
1749
+ else
1750
+ cfg.llm.custom.model = value;
1751
+ console.log(`Custom provider model set to: ${value}`);
1752
+ break;
1753
+ case "custom-key":
1754
+ if (!cfg.llm.custom)
1755
+ cfg.llm.custom = { model: "", baseUrl: "", apiKey: value };
1756
+ else
1757
+ cfg.llm.custom.apiKey = value;
1758
+ console.log(`Custom provider API key set.`);
1759
+ break;
1703
1760
  case "recall": {
1704
1761
  // gnosys config set recall <field> <value>
1705
1762
  // Supported: recall aggressive true/false, recall maxMemories <n>, recall minRelevance <n>
@@ -1916,17 +1973,51 @@ program
1916
1973
  .action(async () => {
1917
1974
  const currentVersion = pkg.version;
1918
1975
  console.log(`Gnosys v${currentVersion} — upgrading registered projects...\n`);
1919
- // 1. Read registered projects
1976
+ // 1. Read registered projects from file registry AND central DB
1920
1977
  const home = process.env.HOME || process.env.USERPROFILE || "/tmp";
1921
1978
  const registryPath = path.join(home, ".config", "gnosys", "projects.json");
1922
- let projects = [];
1979
+ let fileProjects = [];
1923
1980
  try {
1924
- projects = JSON.parse(await fs.readFile(registryPath, "utf-8"));
1981
+ fileProjects = JSON.parse(await fs.readFile(registryPath, "utf-8"));
1925
1982
  }
1926
1983
  catch {
1984
+ // No file registry yet
1985
+ }
1986
+ // Also check central DB for projects not in the file registry
1987
+ let dbProjects = [];
1988
+ try {
1989
+ const centralDb = GnosysDB.openCentral();
1990
+ if (centralDb.isAvailable()) {
1991
+ const allProjects = centralDb.getAllProjects();
1992
+ dbProjects = allProjects.map((p) => p.working_directory);
1993
+ centralDb.close();
1994
+ }
1995
+ }
1996
+ catch {
1997
+ // non-critical
1998
+ }
1999
+ // Merge: deduplicate by resolved path
2000
+ const seen = new Set();
2001
+ const projects = [];
2002
+ for (const p of [...fileProjects, ...dbProjects]) {
2003
+ const resolved = path.resolve(p);
2004
+ if (!seen.has(resolved)) {
2005
+ seen.add(resolved);
2006
+ projects.push(resolved);
2007
+ }
2008
+ }
2009
+ if (projects.length === 0) {
1927
2010
  console.log("No registered projects found. Run 'gnosys init' in each project first.");
1928
2011
  return;
1929
2012
  }
2013
+ // Sync the merged list back to file registry
2014
+ try {
2015
+ await fs.mkdir(path.dirname(registryPath), { recursive: true });
2016
+ await fs.writeFile(registryPath, JSON.stringify(projects, null, 2), "utf-8");
2017
+ }
2018
+ catch {
2019
+ // non-critical
2020
+ }
1930
2021
  // 2. Iterate and upgrade each project that exists on this machine
1931
2022
  const upgraded = [];
1932
2023
  const skipped = [];
@@ -2156,69 +2247,22 @@ program
2156
2247
  console.log("");
2157
2248
  // Check all LLM providers
2158
2249
  console.log("LLM Connectivity:");
2159
- // Check Anthropic
2160
- const anthropicStatus = isProviderAvailable(cfg, "anthropic");
2161
- if (anthropicStatus.available) {
2162
- try {
2163
- const provider = getLLMProvider({ ...cfg, llm: { ...cfg.llm, defaultProvider: "anthropic" } });
2164
- await provider.testConnection();
2165
- console.log(` Anthropic: ✓ connected (${cfg.llm.anthropic.model})`);
2166
- }
2167
- catch (err) {
2168
- console.log(` Anthropic: ✗ ${err instanceof Error ? err.message : String(err)}`);
2169
- }
2170
- }
2171
- else {
2172
- console.log(` Anthropic: — ${anthropicStatus.error}`);
2173
- }
2174
- // Check Ollama
2175
- try {
2176
- const ollamaProvider = getLLMProvider({ ...cfg, llm: { ...cfg.llm, defaultProvider: "ollama" } });
2177
- await ollamaProvider.testConnection();
2178
- console.log(` Ollama: ✓ connected (${cfg.llm.ollama.model} at ${cfg.llm.ollama.baseUrl})`);
2179
- }
2180
- catch (err) {
2181
- console.log(` Ollama: ✗ ${err instanceof Error ? err.message : String(err)}`);
2182
- }
2183
- // Check Groq
2184
- const groqStatus = isProviderAvailable(cfg, "groq");
2185
- if (groqStatus.available) {
2186
- try {
2187
- const provider = getLLMProvider({ ...cfg, llm: { ...cfg.llm, defaultProvider: "groq" } });
2188
- await provider.testConnection();
2189
- console.log(` Groq: ✓ connected (${cfg.llm.groq.model})`);
2190
- }
2191
- catch (err) {
2192
- console.log(` Groq: ✗ ${err instanceof Error ? err.message : String(err)}`);
2250
+ for (const providerName of ALL_PROVIDERS) {
2251
+ const status = isProviderAvailable(cfg, providerName);
2252
+ if (!status.available) {
2253
+ console.log(` ${providerName}: — ${status.error}`);
2254
+ continue;
2193
2255
  }
2194
- }
2195
- else {
2196
- console.log(` Groq: — ${groqStatus.error}`);
2197
- }
2198
- // Check OpenAI
2199
- const openaiStatus = isProviderAvailable(cfg, "openai");
2200
- if (openaiStatus.available) {
2201
2256
  try {
2202
- const provider = getLLMProvider({ ...cfg, llm: { ...cfg.llm, defaultProvider: "openai" } });
2257
+ const provider = getLLMProvider({ ...cfg, llm: { ...cfg.llm, defaultProvider: providerName } });
2203
2258
  await provider.testConnection();
2204
- console.log(` OpenAI: connected (${cfg.llm.openai.model})`);
2259
+ const model = getProviderModel(cfg, providerName);
2260
+ console.log(` ${providerName}: ✓ connected (${model})`);
2205
2261
  }
2206
2262
  catch (err) {
2207
- console.log(` OpenAI: ✗ ${err instanceof Error ? err.message : String(err)}`);
2263
+ console.log(` ${providerName}: ✗ ${err instanceof Error ? err.message : String(err)}`);
2208
2264
  }
2209
2265
  }
2210
- else {
2211
- console.log(` OpenAI: — ${openaiStatus.error}`);
2212
- }
2213
- // Check LM Studio
2214
- try {
2215
- const lmsProvider = getLLMProvider({ ...cfg, llm: { ...cfg.llm, defaultProvider: "lmstudio" } });
2216
- await lmsProvider.testConnection();
2217
- console.log(` LM Studio: ✓ connected (${cfg.llm.lmstudio.model} at ${cfg.llm.lmstudio.baseUrl})`);
2218
- }
2219
- catch (err) {
2220
- console.log(` LM Studio: ✗ ${err instanceof Error ? err.message : String(err)}`);
2221
- }
2222
2266
  console.log("");
2223
2267
  // Check embeddings
2224
2268
  if (stores.length > 0) {