gnosys 5.3.3 → 5.4.1
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 +80 -8
- package/dist/cli.js +225 -46
- package/dist/cli.js.map +1 -1
- package/dist/index.js +136 -97
- package/dist/index.js.map +1 -1
- package/dist/lib/config.d.ts +0 -5
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +71 -9
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/dashboard.d.ts.map +1 -1
- package/dist/lib/dashboard.js +137 -94
- package/dist/lib/dashboard.js.map +1 -1
- package/dist/lib/db.d.ts +54 -6
- package/dist/lib/db.d.ts.map +1 -1
- package/dist/lib/db.js +157 -25
- package/dist/lib/db.js.map +1 -1
- package/dist/lib/ingest.d.ts.map +1 -1
- package/dist/lib/ingest.js +24 -4
- package/dist/lib/ingest.js.map +1 -1
- package/dist/lib/lock.d.ts +5 -0
- package/dist/lib/lock.d.ts.map +1 -1
- package/dist/lib/lock.js +9 -0
- package/dist/lib/lock.js.map +1 -1
- package/dist/lib/modelValidation.d.ts +22 -0
- package/dist/lib/modelValidation.d.ts.map +1 -0
- package/dist/lib/modelValidation.js +157 -0
- package/dist/lib/modelValidation.js.map +1 -0
- package/dist/lib/paths.d.ts +32 -0
- package/dist/lib/paths.d.ts.map +1 -0
- package/dist/lib/paths.js +44 -0
- package/dist/lib/paths.js.map +1 -0
- package/dist/lib/remote.d.ts +15 -0
- package/dist/lib/remote.d.ts.map +1 -1
- package/dist/lib/remote.js +85 -0
- package/dist/lib/remote.js.map +1 -1
- package/dist/lib/remoteWizard.d.ts +2 -1
- package/dist/lib/remoteWizard.d.ts.map +1 -1
- package/dist/lib/remoteWizard.js +6 -3
- package/dist/lib/remoteWizard.js.map +1 -1
- package/dist/lib/setup.d.ts +25 -0
- package/dist/lib/setup.d.ts.map +1 -1
- package/dist/lib/setup.js +459 -25
- package/dist/lib/setup.js.map +1 -1
- package/dist/lib/store.d.ts +2 -0
- package/dist/lib/store.d.ts.map +1 -1
- package/dist/lib/store.js +4 -11
- package/dist/lib/store.js.map +1 -1
- package/dist/postinstall.js +2 -2
- package/dist/postinstall.js.map +1 -1
- package/dist/sandbox/helper-template.d.ts.map +1 -1
- package/dist/sandbox/helper-template.js +8 -2
- package/dist/sandbox/helper-template.js.map +1 -1
- package/dist/sandbox/server.d.ts.map +1 -1
- package/dist/sandbox/server.js +2 -2
- package/dist/sandbox/server.js.map +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
|
|
25
25
|
Gnosys is **sandbox-first**: a persistent background process holds the database connection while agents import a tiny helper library and call memory operations like normal code — no MCP schemas, no round-trips, near-zero context cost. The central brain at `~/.gnosys/gnosys.db` unifies all projects, user preferences, and global knowledge. Federated search ranks results across scopes with tier boosting and recency awareness. The **Web Knowledge Base** turns any website into a searchable knowledge base for serverless chatbots — pre-computed JSON index, zero runtime dependencies. **Multimodal ingestion** handles PDFs, images, audio, and video. **Portfolio Dashboard** gives a bird's-eye view of all projects. Process tracing builds call chains from source code. Dream Mode consolidates knowledge during idle time. One-command export regenerates a full Obsidian vault.
|
|
26
26
|
|
|
27
|
-
It also runs as a CLI and a complete MCP server that drops straight into Cursor, Claude Desktop, Claude Code,
|
|
27
|
+
It also runs as a CLI and a complete MCP server that drops straight into Cursor, Claude Desktop (Chat / Cowork / Code), Claude Code, Codex, Gemini CLI, Antigravity, or any MCP client.
|
|
28
28
|
|
|
29
29
|
No vector DBs. No black boxes. No external services. Just SQLite and optional Obsidian export — the way knowledge should be.
|
|
30
30
|
|
|
@@ -53,7 +53,7 @@ Gnosys takes a different approach: the central brain is a single SQLite database
|
|
|
53
53
|
- **Bulk import** — CSV, JSON, JSONL. Import entire datasets in seconds.
|
|
54
54
|
- **Obsidian-native** — `gnosys export` generates a full vault with YAML frontmatter, `[[wikilinks]]`, summaries, and graph data.
|
|
55
55
|
- **Multi-machine sync (v5.3.0)** — share your `gnosys.db` across machines via NAS or shared drive. Local cache for speed, remote source of truth for consistency. Built-in conflict detection with skip-and-flag resolution. Run `gnosys remote configure` to set up.
|
|
56
|
-
- **MCP-compatible** — also runs as a full MCP server that drops into Cursor, Claude Desktop, Claude Code,
|
|
56
|
+
- **MCP-compatible** — also runs as a full MCP server that drops into Cursor, Claude Desktop (Chat / Cowork / Code), Claude Code, Codex, Gemini CLI, Antigravity, or any MCP client.
|
|
57
57
|
- **Zero infrastructure** — no external databases, no Docker (unless you want it), no cloud services. Just `npm install`.
|
|
58
58
|
|
|
59
59
|
> For the complete CLI reference and detailed guides, see the **[User Guide](https://gnosys.ai/guide.html)**.
|
|
@@ -235,9 +235,47 @@ This improves your site's visibility in AI-powered search results and enables LL
|
|
|
235
235
|
|
|
236
236
|
## MCP Server Setup
|
|
237
237
|
|
|
238
|
-
|
|
238
|
+
The fastest way to wire gnosys into any supported client is to run `gnosys init <ide>` from the project directory you want memory-enabled. Examples:
|
|
239
239
|
|
|
240
|
-
|
|
240
|
+
```bash
|
|
241
|
+
gnosys init claude-desktop # Claude Desktop (covers Chat, Cowork, and Code)
|
|
242
|
+
gnosys init claude # Claude Code CLI
|
|
243
|
+
gnosys init cursor # Cursor
|
|
244
|
+
gnosys init codex # Codex
|
|
245
|
+
gnosys init gemini-cli # Gemini CLI
|
|
246
|
+
gnosys init antigravity # Google Antigravity
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
This does two things at once:
|
|
250
|
+
1. Wires gnosys into the IDE's MCP config (idempotent — safe to re-run).
|
|
251
|
+
2. Initializes the current directory as a gnosys project (creates `.gnosys/gnosys.json`, registers it in the central DB) so your memories can be scoped to it.
|
|
252
|
+
|
|
253
|
+
### One-time vs. per-project
|
|
254
|
+
|
|
255
|
+
The IDE wiring writes to a **user-level** config file, so it only needs to happen **once**. Re-running it in another project just re-merges the same `mcpServers.gnosys` entry — harmless.
|
|
256
|
+
|
|
257
|
+
The project registration is **per-directory**: every codebase you want to be memory-aware of needs its own `gnosys init`. From then on, agents pass `projectRoot: "/path/to/project"` to gnosys MCP tools to scope memory to that codebase.
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
# Once, anywhere — wires Claude Desktop's MCP config:
|
|
261
|
+
gnosys init claude-desktop
|
|
262
|
+
|
|
263
|
+
# Once per project — registers the codebase in the central DB:
|
|
264
|
+
cd /path/to/project-a && gnosys init
|
|
265
|
+
cd /path/to/project-b && gnosys init
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
> **Cowork users:** Cowork sessions don't have a working directory like a CLI does. The agent in Cowork uses whichever `projectRoot` it's told to use (typically auto-detected from open files or set via the system prompt). The "every working directory" question doesn't apply to Cowork itself — only to the projects you want memory-enabled. Run `gnosys init claude-desktop` once globally; run `gnosys init` per project.
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
### Manual config (if you prefer)
|
|
273
|
+
|
|
274
|
+
If you'd rather edit configs by hand, here's where each client looks for MCP servers.
|
|
275
|
+
|
|
276
|
+
#### Claude Desktop (Chat, Cowork, and Code share the same config)
|
|
277
|
+
|
|
278
|
+
Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS), `%APPDATA%\Claude\claude_desktop_config.json` (Windows), or `~/.config/Claude/claude_desktop_config.json` (Linux):
|
|
241
279
|
|
|
242
280
|
```json
|
|
243
281
|
{
|
|
@@ -250,9 +288,11 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
|
250
288
|
}
|
|
251
289
|
```
|
|
252
290
|
|
|
253
|
-
|
|
291
|
+
Restart Claude Desktop after editing.
|
|
292
|
+
|
|
293
|
+
#### Cursor
|
|
254
294
|
|
|
255
|
-
Add to `.cursor/mcp.json
|
|
295
|
+
Add to `.cursor/mcp.json` in your project:
|
|
256
296
|
|
|
257
297
|
```json
|
|
258
298
|
{
|
|
@@ -265,13 +305,13 @@ Add to `.cursor/mcp.json`:
|
|
|
265
305
|
}
|
|
266
306
|
```
|
|
267
307
|
|
|
268
|
-
|
|
308
|
+
#### Claude Code
|
|
269
309
|
|
|
270
310
|
```bash
|
|
271
311
|
claude mcp add gnosys gnosys serve
|
|
272
312
|
```
|
|
273
313
|
|
|
274
|
-
|
|
314
|
+
#### Codex
|
|
275
315
|
|
|
276
316
|
Add to `.codex/config.toml`:
|
|
277
317
|
|
|
@@ -281,6 +321,38 @@ type = "local"
|
|
|
281
321
|
command = ["gnosys", "serve"]
|
|
282
322
|
```
|
|
283
323
|
|
|
324
|
+
#### Gemini CLI
|
|
325
|
+
|
|
326
|
+
Add to `~/.gemini/settings.json` (preserves any existing settings):
|
|
327
|
+
|
|
328
|
+
```json
|
|
329
|
+
{
|
|
330
|
+
"mcpServers": {
|
|
331
|
+
"gnosys": {
|
|
332
|
+
"command": "gnosys",
|
|
333
|
+
"args": ["serve"]
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
#### Antigravity
|
|
340
|
+
|
|
341
|
+
Add to `~/.gemini/antigravity/mcp_config.json`:
|
|
342
|
+
|
|
343
|
+
```json
|
|
344
|
+
{
|
|
345
|
+
"mcpServers": {
|
|
346
|
+
"gnosys": {
|
|
347
|
+
"command": "gnosys",
|
|
348
|
+
"args": ["serve"]
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
Antigravity reloads MCP servers automatically when you save the file.
|
|
355
|
+
|
|
284
356
|
> **Note:** API keys are configured via `gnosys setup` (macOS Keychain, environment variable, or `~/.config/gnosys/.env`). See [LLM Provider Setup](https://gnosys.ai/guide.html#guide-llm-provider-setup) in the User Guide.
|
|
285
357
|
|
|
286
358
|
---
|
package/dist/cli.js
CHANGED
|
@@ -11,6 +11,7 @@ import { fileURLToPath } from "url";
|
|
|
11
11
|
import dotenv from "dotenv";
|
|
12
12
|
import { readFileSync, existsSync, copyFileSync } from "fs";
|
|
13
13
|
import { GnosysResolver } from "./lib/resolver.js";
|
|
14
|
+
import { getGnosysHome } from "./lib/paths.js";
|
|
14
15
|
import { GnosysSearch } from "./lib/search.js";
|
|
15
16
|
import { GnosysTagRegistry } from "./lib/tags.js";
|
|
16
17
|
import { GnosysIngestion } from "./lib/ingest.js";
|
|
@@ -469,10 +470,12 @@ program
|
|
|
469
470
|
}
|
|
470
471
|
}
|
|
471
472
|
});
|
|
472
|
-
// ─── gnosys setup
|
|
473
|
-
program
|
|
473
|
+
// ─── gnosys setup (parent command) ──────────────────────────────────────
|
|
474
|
+
const setupCmd = program
|
|
474
475
|
.command("setup")
|
|
475
|
-
.description("
|
|
476
|
+
.description("Configure Gnosys — LLM provider, models, remote sync, and IDE integration");
|
|
477
|
+
// Bare `gnosys setup` runs the full interactive wizard
|
|
478
|
+
setupCmd
|
|
476
479
|
.option("--non-interactive", "Skip prompts, use defaults (for CI/scripting)")
|
|
477
480
|
.action(async (opts) => {
|
|
478
481
|
const { runSetup } = await import("./lib/setup.js");
|
|
@@ -481,10 +484,65 @@ program
|
|
|
481
484
|
nonInteractive: opts.nonInteractive,
|
|
482
485
|
});
|
|
483
486
|
});
|
|
487
|
+
// `gnosys setup models` — just configure LLM provider/model/key
|
|
488
|
+
setupCmd
|
|
489
|
+
.command("models")
|
|
490
|
+
.description("Update LLM provider and model configuration")
|
|
491
|
+
.option("-p, --provider <name>", "Set provider directly (anthropic, openai, xai, groq, mistral, ollama, lmstudio, custom)")
|
|
492
|
+
.option("-m, --model <name>", "Set model name directly")
|
|
493
|
+
.option("--no-validate", "Skip the test API call")
|
|
494
|
+
.action(async (opts) => {
|
|
495
|
+
const { runModelsSetup } = await import("./lib/setup.js");
|
|
496
|
+
await runModelsSetup({
|
|
497
|
+
directory: process.cwd(),
|
|
498
|
+
provider: opts.provider,
|
|
499
|
+
model: opts.model,
|
|
500
|
+
validate: opts.validate,
|
|
501
|
+
});
|
|
502
|
+
});
|
|
503
|
+
// `gnosys setup remote` — configure remote sync (alias for `gnosys remote configure`)
|
|
504
|
+
setupCmd
|
|
505
|
+
.command("remote")
|
|
506
|
+
.description("Configure multi-machine sync (alias for 'gnosys remote configure')")
|
|
507
|
+
.option("--path <path>", "Set remote path directly (non-interactive)")
|
|
508
|
+
.action(async (opts) => {
|
|
509
|
+
const { GnosysDB } = await import("./lib/db.js");
|
|
510
|
+
// Sync configuration needs explicit local DB access (not auto-routed remote).
|
|
511
|
+
const db = GnosysDB.openLocal();
|
|
512
|
+
if (!db.isAvailable()) {
|
|
513
|
+
console.error("Central DB not available.");
|
|
514
|
+
db.close();
|
|
515
|
+
process.exit(1);
|
|
516
|
+
}
|
|
517
|
+
try {
|
|
518
|
+
if (opts.path) {
|
|
519
|
+
const { configureFromPath } = await import("./lib/remoteWizard.js");
|
|
520
|
+
await configureFromPath(db, opts.path);
|
|
521
|
+
}
|
|
522
|
+
else {
|
|
523
|
+
const { runConfigureWizard } = await import("./lib/remoteWizard.js");
|
|
524
|
+
await runConfigureWizard(db);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
finally {
|
|
528
|
+
db.close();
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
// ─── gnosys models (top-level shortcut) ─────────────────────────────────
|
|
532
|
+
program
|
|
533
|
+
.command("models")
|
|
534
|
+
.description("Quick model operations — list available, refresh cache, or set the default")
|
|
535
|
+
.option("--list", "List available models for the current provider")
|
|
536
|
+
.option("--refresh", "Refresh model list from OpenRouter (clears the cache)")
|
|
537
|
+
.option("--set <model>", "Set the default model for the current provider")
|
|
538
|
+
.action(async (opts) => {
|
|
539
|
+
const { runModelsCommand } = await import("./lib/setup.js");
|
|
540
|
+
await runModelsCommand(opts);
|
|
541
|
+
});
|
|
484
542
|
// ─── gnosys init ─────────────────────────────────────────────────────────
|
|
485
543
|
program
|
|
486
544
|
.command("init [ide]")
|
|
487
|
-
.description("Initialize Gnosys in the current directory. Optionally specify IDE: cursor, claude,
|
|
545
|
+
.description("Initialize Gnosys in the current directory. Optionally specify IDE: cursor, claude, claude-desktop, codex, gemini-cli, or antigravity to force IDE setup.")
|
|
488
546
|
.option("-d, --directory <dir>", "Target directory (default: cwd)")
|
|
489
547
|
.option("-n, --name <name>", "Project name (default: directory basename)")
|
|
490
548
|
.action(async (ide, opts) => {
|
|
@@ -591,13 +649,15 @@ program
|
|
|
591
649
|
}
|
|
592
650
|
// If a specific IDE was requested, force-create its config
|
|
593
651
|
if (ide) {
|
|
594
|
-
const validIdes = ["cursor", "claude", "codex"];
|
|
652
|
+
const validIdes = ["cursor", "claude", "claude-desktop", "codex", "gemini-cli", "antigravity"];
|
|
595
653
|
const normalizedIde = ide.toLowerCase();
|
|
596
654
|
if (!validIdes.includes(normalizedIde)) {
|
|
597
655
|
console.log(`\nUnknown IDE: "${ide}". Valid options: ${validIdes.join(", ")}`);
|
|
598
656
|
}
|
|
599
657
|
else {
|
|
600
658
|
const { configureCursor, configureClaudeCode, configureCodex } = await import("./lib/projectIdentity.js");
|
|
659
|
+
// Cursor/Claude/Codex have IDE-specific session hooks. Gemini CLI and
|
|
660
|
+
// Antigravity don't yet, so we skip the hook step for them.
|
|
601
661
|
let result;
|
|
602
662
|
switch (normalizedIde) {
|
|
603
663
|
case "cursor":
|
|
@@ -615,28 +675,30 @@ program
|
|
|
615
675
|
console.log(` ${result.details}`);
|
|
616
676
|
console.log(` File: ${result.filePath}`);
|
|
617
677
|
}
|
|
618
|
-
//
|
|
678
|
+
// Set up MCP config for the IDE
|
|
619
679
|
const { setupIDE } = await import("./lib/setup.js");
|
|
620
680
|
const mcp = await setupIDE(normalizedIde, targetDir);
|
|
621
681
|
if (mcp.success) {
|
|
622
682
|
console.log(` MCP: ${mcp.message}`);
|
|
623
683
|
}
|
|
624
|
-
// Update agentRulesTarget in gnosys.json
|
|
625
|
-
const
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
684
|
+
// Update agentRulesTarget in gnosys.json (only for IDEs with rules files)
|
|
685
|
+
const targetMap = {
|
|
686
|
+
cursor: ".cursor/rules/gnosys.mdc",
|
|
687
|
+
claude: "CLAUDE.md",
|
|
688
|
+
codex: "CODEX.md",
|
|
689
|
+
};
|
|
690
|
+
if (targetMap[normalizedIde]) {
|
|
691
|
+
const identityPath = path.join(storePath, "gnosys.json");
|
|
692
|
+
try {
|
|
693
|
+
const identityContent = await fs.readFile(identityPath, "utf-8");
|
|
694
|
+
const identity = JSON.parse(identityContent);
|
|
695
|
+
identity.agentRulesTarget = targetMap[normalizedIde];
|
|
696
|
+
await fs.writeFile(identityPath, JSON.stringify(identity, null, 2) + "\n", "utf-8");
|
|
697
|
+
console.log(` Config: agentRulesTarget → ${identity.agentRulesTarget}`);
|
|
698
|
+
}
|
|
699
|
+
catch {
|
|
700
|
+
// Non-critical
|
|
701
|
+
}
|
|
640
702
|
}
|
|
641
703
|
}
|
|
642
704
|
}
|
|
@@ -1652,14 +1714,60 @@ program
|
|
|
1652
1714
|
.command("graph")
|
|
1653
1715
|
.description("Show the full cross-reference graph across all memories")
|
|
1654
1716
|
.action(async () => {
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1717
|
+
// v5.4.1: Query the central DB directly. Previously this used the
|
|
1718
|
+
// filesystem resolver, which returns nothing in v5.x DB-only mode
|
|
1719
|
+
// because memories no longer live as markdown files.
|
|
1720
|
+
let centralDb = null;
|
|
1721
|
+
try {
|
|
1722
|
+
centralDb = GnosysDB.openCentral();
|
|
1723
|
+
if (!centralDb.isAvailable()) {
|
|
1724
|
+
console.error("Central DB not available.");
|
|
1725
|
+
process.exit(1);
|
|
1726
|
+
}
|
|
1727
|
+
const dbMemories = centralDb.getAllMemories();
|
|
1728
|
+
if (dbMemories.length === 0) {
|
|
1729
|
+
console.log("No memories found.");
|
|
1730
|
+
return;
|
|
1731
|
+
}
|
|
1732
|
+
// Adapt DbMemory → legacy Memory shape that buildLinkGraph expects.
|
|
1733
|
+
// The graph builder only reads id, title, content, and synthesises
|
|
1734
|
+
// a filesystem-style path for display.
|
|
1735
|
+
const adapted = dbMemories.map((m) => {
|
|
1736
|
+
let parsedTags = [];
|
|
1737
|
+
try {
|
|
1738
|
+
parsedTags = JSON.parse(m.tags);
|
|
1739
|
+
}
|
|
1740
|
+
catch {
|
|
1741
|
+
parsedTags = [];
|
|
1742
|
+
}
|
|
1743
|
+
const relativePath = `${m.category}/${m.id}.md`;
|
|
1744
|
+
return {
|
|
1745
|
+
frontmatter: {
|
|
1746
|
+
id: m.id,
|
|
1747
|
+
title: m.title,
|
|
1748
|
+
category: m.category,
|
|
1749
|
+
tags: parsedTags,
|
|
1750
|
+
relevance: m.relevance,
|
|
1751
|
+
author: m.author,
|
|
1752
|
+
authority: m.authority,
|
|
1753
|
+
confidence: m.confidence,
|
|
1754
|
+
created: m.created,
|
|
1755
|
+
modified: m.modified,
|
|
1756
|
+
last_reviewed: m.modified,
|
|
1757
|
+
status: m.status,
|
|
1758
|
+
supersedes: m.supersedes,
|
|
1759
|
+
},
|
|
1760
|
+
content: m.content,
|
|
1761
|
+
filePath: relativePath,
|
|
1762
|
+
relativePath,
|
|
1763
|
+
};
|
|
1764
|
+
});
|
|
1765
|
+
const graph = buildLinkGraph(adapted);
|
|
1766
|
+
console.log(formatGraphSummary(graph));
|
|
1767
|
+
}
|
|
1768
|
+
finally {
|
|
1769
|
+
centralDb?.close();
|
|
1660
1770
|
}
|
|
1661
|
-
const graph = buildLinkGraph(allMemories);
|
|
1662
|
-
console.log(formatGraphSummary(graph));
|
|
1663
1771
|
});
|
|
1664
1772
|
// ─── gnosys bootstrap <sourceDir> ────────────────────────────────────────
|
|
1665
1773
|
program
|
|
@@ -2480,7 +2588,8 @@ remoteCmd
|
|
|
2480
2588
|
.action(async (opts) => {
|
|
2481
2589
|
let centralDb = null;
|
|
2482
2590
|
try {
|
|
2483
|
-
|
|
2591
|
+
// Sync operations need explicit local DB access (not auto-routed remote).
|
|
2592
|
+
centralDb = GnosysDB.openLocal();
|
|
2484
2593
|
if (!centralDb.isAvailable()) {
|
|
2485
2594
|
console.error("Central DB not available.");
|
|
2486
2595
|
process.exit(1);
|
|
@@ -2531,7 +2640,7 @@ remoteCmd
|
|
|
2531
2640
|
.action(async (opts) => {
|
|
2532
2641
|
let centralDb = null;
|
|
2533
2642
|
try {
|
|
2534
|
-
centralDb = GnosysDB.
|
|
2643
|
+
centralDb = GnosysDB.openLocal();
|
|
2535
2644
|
if (!centralDb.isAvailable()) {
|
|
2536
2645
|
console.error("Central DB not available.");
|
|
2537
2646
|
process.exit(1);
|
|
@@ -2545,7 +2654,8 @@ remoteCmd
|
|
|
2545
2654
|
const sync = new RemoteSync(centralDb, remotePath);
|
|
2546
2655
|
const result = await sync.push({ strategy: opts.newerWins ? "newer-wins" : "skip-and-flag" });
|
|
2547
2656
|
sync.closeRemote();
|
|
2548
|
-
|
|
2657
|
+
const projParts = (result.projectsPushed || 0) > 0 ? ` | Projects pushed: ${result.projectsPushed}` : "";
|
|
2658
|
+
console.log(`Pushed: ${result.pushed} | Skipped: ${result.skipped} | Conflicts: ${result.conflicts.length}${projParts}`);
|
|
2549
2659
|
if (result.errors.length > 0) {
|
|
2550
2660
|
console.log("\nErrors:");
|
|
2551
2661
|
for (const e of result.errors)
|
|
@@ -2572,7 +2682,7 @@ remoteCmd
|
|
|
2572
2682
|
.action(async (opts) => {
|
|
2573
2683
|
let centralDb = null;
|
|
2574
2684
|
try {
|
|
2575
|
-
centralDb = GnosysDB.
|
|
2685
|
+
centralDb = GnosysDB.openLocal();
|
|
2576
2686
|
if (!centralDb.isAvailable()) {
|
|
2577
2687
|
console.error("Central DB not available.");
|
|
2578
2688
|
process.exit(1);
|
|
@@ -2586,7 +2696,8 @@ remoteCmd
|
|
|
2586
2696
|
const sync = new RemoteSync(centralDb, remotePath);
|
|
2587
2697
|
const result = await sync.pull({ strategy: opts.newerWins ? "newer-wins" : "skip-and-flag" });
|
|
2588
2698
|
sync.closeRemote();
|
|
2589
|
-
|
|
2699
|
+
const projParts = (result.projectsPulled || 0) > 0 ? ` | Projects pulled: ${result.projectsPulled}` : "";
|
|
2700
|
+
console.log(`Pulled: ${result.pulled} | Skipped: ${result.skipped} | Conflicts: ${result.conflicts.length}${projParts}`);
|
|
2590
2701
|
if (result.errors.length > 0) {
|
|
2591
2702
|
console.log("\nErrors:");
|
|
2592
2703
|
for (const e of result.errors)
|
|
@@ -2609,7 +2720,7 @@ remoteCmd
|
|
|
2609
2720
|
.action(async (opts) => {
|
|
2610
2721
|
let centralDb = null;
|
|
2611
2722
|
try {
|
|
2612
|
-
centralDb = GnosysDB.
|
|
2723
|
+
centralDb = GnosysDB.openLocal();
|
|
2613
2724
|
if (!centralDb.isAvailable()) {
|
|
2614
2725
|
if (!opts.auto)
|
|
2615
2726
|
console.error("Central DB not available.");
|
|
@@ -2629,7 +2740,10 @@ remoteCmd
|
|
|
2629
2740
|
});
|
|
2630
2741
|
sync.closeRemote();
|
|
2631
2742
|
if (!opts.auto || result.conflicts.length > 0 || result.errors.length > 0) {
|
|
2632
|
-
|
|
2743
|
+
const pp = result.projectsPushed || 0;
|
|
2744
|
+
const pl = result.projectsPulled || 0;
|
|
2745
|
+
const projParts = (pp + pl) > 0 ? ` | Projects: ↑${pp}/↓${pl}` : "";
|
|
2746
|
+
console.log(`Pushed: ${result.pushed} | Pulled: ${result.pulled} | Conflicts: ${result.conflicts.length}${projParts}`);
|
|
2633
2747
|
if (result.errors.length > 0) {
|
|
2634
2748
|
console.log("\nErrors:");
|
|
2635
2749
|
for (const e of result.errors)
|
|
@@ -2656,7 +2770,7 @@ remoteCmd
|
|
|
2656
2770
|
.action(async (memoryId, opts) => {
|
|
2657
2771
|
let centralDb = null;
|
|
2658
2772
|
try {
|
|
2659
|
-
centralDb = GnosysDB.
|
|
2773
|
+
centralDb = GnosysDB.openLocal();
|
|
2660
2774
|
if (!centralDb.isAvailable()) {
|
|
2661
2775
|
console.error("Central DB not available.");
|
|
2662
2776
|
process.exit(1);
|
|
@@ -2698,7 +2812,7 @@ remoteCmd
|
|
|
2698
2812
|
.action(async (opts) => {
|
|
2699
2813
|
let centralDb = null;
|
|
2700
2814
|
try {
|
|
2701
|
-
centralDb = GnosysDB.
|
|
2815
|
+
centralDb = GnosysDB.openLocal();
|
|
2702
2816
|
if (!centralDb.isAvailable()) {
|
|
2703
2817
|
console.error("Central DB not available.");
|
|
2704
2818
|
process.exit(1);
|
|
@@ -3103,7 +3217,7 @@ program
|
|
|
3103
3217
|
.action(async (opts) => {
|
|
3104
3218
|
const projectDir = opts.directory ? path.resolve(opts.directory) : process.cwd();
|
|
3105
3219
|
const storePath = path.join(projectDir, ".gnosys");
|
|
3106
|
-
const globalStorePath =
|
|
3220
|
+
const globalStorePath = getGnosysHome();
|
|
3107
3221
|
// Load config: try project-level first, fall back to global ~/.gnosys/
|
|
3108
3222
|
let cfg;
|
|
3109
3223
|
let configSource;
|
|
@@ -3685,10 +3799,22 @@ program
|
|
|
3685
3799
|
console.log(` Central DB: ${GnosysDB.getCentralDbPath()}`);
|
|
3686
3800
|
});
|
|
3687
3801
|
// ─── gnosys projects ────────────────────────────────────────────────────
|
|
3802
|
+
/**
|
|
3803
|
+
* Returns true if a project's working directory no longer exists on disk.
|
|
3804
|
+
* Used by `gnosys projects` to filter dead entries by default and by
|
|
3805
|
+
* `gnosys projects --prune` to delete them. We deliberately do NOT pattern-
|
|
3806
|
+
* match on tmp paths — active test fixtures live in /var/folders/ and
|
|
3807
|
+
* /tmp/ and we want them visible while they're in use.
|
|
3808
|
+
*/
|
|
3809
|
+
function isDeadProjectDir(dir) {
|
|
3810
|
+
return !existsSync(dir);
|
|
3811
|
+
}
|
|
3688
3812
|
program
|
|
3689
3813
|
.command("projects")
|
|
3690
|
-
.description("List
|
|
3814
|
+
.description("List registered projects from the central DB")
|
|
3691
3815
|
.option("--json", "Output as JSON")
|
|
3816
|
+
.option("--all", "Include dead projects (deleted directories, /tmp/ paths)")
|
|
3817
|
+
.option("--prune", "Delete registry entries whose directory no longer exists or is a tmp path")
|
|
3692
3818
|
.action(async (opts) => {
|
|
3693
3819
|
let centralDb = null;
|
|
3694
3820
|
try {
|
|
@@ -3697,18 +3823,71 @@ program
|
|
|
3697
3823
|
console.error("Central DB not available (better-sqlite3 missing).");
|
|
3698
3824
|
process.exit(1);
|
|
3699
3825
|
}
|
|
3700
|
-
const
|
|
3701
|
-
if (
|
|
3702
|
-
|
|
3826
|
+
const allProjects = centralDb.getAllProjects();
|
|
3827
|
+
if (opts.prune) {
|
|
3828
|
+
// Find and delete dead projects
|
|
3829
|
+
const deadProjects = allProjects.filter((p) => isDeadProjectDir(p.working_directory));
|
|
3830
|
+
for (const p of deadProjects) {
|
|
3831
|
+
centralDb.deleteProject(p.id);
|
|
3832
|
+
}
|
|
3833
|
+
outputResult(!!opts.json, {
|
|
3834
|
+
deleted: deadProjects.length,
|
|
3835
|
+
remaining: allProjects.length - deadProjects.length,
|
|
3836
|
+
deletedProjects: deadProjects.map((p) => ({ id: p.id, name: p.name, directory: p.working_directory })),
|
|
3837
|
+
}, () => {
|
|
3838
|
+
if (deadProjects.length === 0) {
|
|
3839
|
+
console.log("No dead projects to prune.");
|
|
3840
|
+
}
|
|
3841
|
+
else {
|
|
3842
|
+
const DIM = "\x1b[2m";
|
|
3843
|
+
const RESET = "\x1b[0m";
|
|
3844
|
+
console.log(`Pruned ${deadProjects.length} dead project(s):\n`);
|
|
3845
|
+
for (const p of deadProjects) {
|
|
3846
|
+
console.log(` ${p.name} ${DIM}${p.working_directory}${RESET}`);
|
|
3847
|
+
}
|
|
3848
|
+
console.log();
|
|
3849
|
+
console.log(`${allProjects.length - deadProjects.length} project(s) remain.`);
|
|
3850
|
+
}
|
|
3851
|
+
});
|
|
3852
|
+
return;
|
|
3853
|
+
}
|
|
3854
|
+
// Normal listing — filter dead projects by default
|
|
3855
|
+
const visibleProjects = opts.all
|
|
3856
|
+
? allProjects
|
|
3857
|
+
: allProjects.filter((p) => !isDeadProjectDir(p.working_directory));
|
|
3858
|
+
if (visibleProjects.length === 0) {
|
|
3859
|
+
const deadCount = allProjects.length;
|
|
3860
|
+
outputResult(!!opts.json, {
|
|
3861
|
+
count: 0,
|
|
3862
|
+
totalRegistered: deadCount,
|
|
3863
|
+
deadCount,
|
|
3864
|
+
projects: [],
|
|
3865
|
+
}, () => {
|
|
3866
|
+
if (deadCount === 0) {
|
|
3867
|
+
console.log("No projects registered. Run 'gnosys init' in a project directory.");
|
|
3868
|
+
}
|
|
3869
|
+
else {
|
|
3870
|
+
console.log(`No live projects (${deadCount} dead — run 'gnosys projects --all' to see them or 'gnosys projects --prune' to remove them).`);
|
|
3871
|
+
}
|
|
3872
|
+
});
|
|
3703
3873
|
centralDb.close();
|
|
3704
3874
|
return;
|
|
3705
3875
|
}
|
|
3706
|
-
const projectData =
|
|
3876
|
+
const projectData = visibleProjects.map((p) => ({
|
|
3707
3877
|
...p,
|
|
3708
3878
|
memoryCount: centralDb.getMemoriesByProject(p.id).length,
|
|
3709
3879
|
}));
|
|
3710
|
-
|
|
3711
|
-
|
|
3880
|
+
const deadCount = allProjects.length - visibleProjects.length;
|
|
3881
|
+
outputResult(!!opts.json, {
|
|
3882
|
+
count: visibleProjects.length,
|
|
3883
|
+
totalRegistered: allProjects.length,
|
|
3884
|
+
deadCount,
|
|
3885
|
+
projects: projectData,
|
|
3886
|
+
}, () => {
|
|
3887
|
+
const header = deadCount > 0 && !opts.all
|
|
3888
|
+
? `${visibleProjects.length} live project(s) (${deadCount} dead hidden — use --all or --prune):\n`
|
|
3889
|
+
: `${visibleProjects.length} registered project(s):\n`;
|
|
3890
|
+
console.log(header);
|
|
3712
3891
|
for (const p of projectData) {
|
|
3713
3892
|
console.log(` ${p.name}`);
|
|
3714
3893
|
console.log(` ID: ${p.id}`);
|