gnosys 5.3.2 → 5.4.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/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("Interactive setup wizard configure LLM provider, API key, model, and IDE integration in one step")
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,64 @@ 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
+ const db = GnosysDB.openCentral();
511
+ if (!db.isAvailable()) {
512
+ console.error("Central DB not available.");
513
+ db.close();
514
+ process.exit(1);
515
+ }
516
+ try {
517
+ if (opts.path) {
518
+ const { configureFromPath } = await import("./lib/remoteWizard.js");
519
+ await configureFromPath(db, opts.path);
520
+ }
521
+ else {
522
+ const { runConfigureWizard } = await import("./lib/remoteWizard.js");
523
+ await runConfigureWizard(db);
524
+ }
525
+ }
526
+ finally {
527
+ db.close();
528
+ }
529
+ });
530
+ // ─── gnosys models (top-level shortcut) ─────────────────────────────────
531
+ program
532
+ .command("models")
533
+ .description("Quick model operations — list available, refresh cache, or set the default")
534
+ .option("--list", "List available models for the current provider")
535
+ .option("--refresh", "Refresh model list from OpenRouter (clears the cache)")
536
+ .option("--set <model>", "Set the default model for the current provider")
537
+ .action(async (opts) => {
538
+ const { runModelsCommand } = await import("./lib/setup.js");
539
+ await runModelsCommand(opts);
540
+ });
484
541
  // ─── gnosys init ─────────────────────────────────────────────────────────
485
542
  program
486
543
  .command("init [ide]")
487
- .description("Initialize Gnosys in the current directory. Optionally specify IDE: cursor, claude, or codex to force IDE setup.")
544
+ .description("Initialize Gnosys in the current directory. Optionally specify IDE: cursor, claude, claude-desktop, codex, gemini-cli, or antigravity to force IDE setup.")
488
545
  .option("-d, --directory <dir>", "Target directory (default: cwd)")
489
546
  .option("-n, --name <name>", "Project name (default: directory basename)")
490
547
  .action(async (ide, opts) => {
@@ -591,13 +648,15 @@ program
591
648
  }
592
649
  // If a specific IDE was requested, force-create its config
593
650
  if (ide) {
594
- const validIdes = ["cursor", "claude", "codex"];
651
+ const validIdes = ["cursor", "claude", "claude-desktop", "codex", "gemini-cli", "antigravity"];
595
652
  const normalizedIde = ide.toLowerCase();
596
653
  if (!validIdes.includes(normalizedIde)) {
597
654
  console.log(`\nUnknown IDE: "${ide}". Valid options: ${validIdes.join(", ")}`);
598
655
  }
599
656
  else {
600
657
  const { configureCursor, configureClaudeCode, configureCodex } = await import("./lib/projectIdentity.js");
658
+ // Cursor/Claude/Codex have IDE-specific session hooks. Gemini CLI and
659
+ // Antigravity don't yet, so we skip the hook step for them.
601
660
  let result;
602
661
  switch (normalizedIde) {
603
662
  case "cursor":
@@ -615,28 +674,30 @@ program
615
674
  console.log(` ${result.details}`);
616
675
  console.log(` File: ${result.filePath}`);
617
676
  }
618
- // Also set up MCP config for the IDE
677
+ // Set up MCP config for the IDE
619
678
  const { setupIDE } = await import("./lib/setup.js");
620
679
  const mcp = await setupIDE(normalizedIde, targetDir);
621
680
  if (mcp.success) {
622
681
  console.log(` MCP: ${mcp.message}`);
623
682
  }
624
- // Update agentRulesTarget in gnosys.json
625
- const identityPath = path.join(storePath, "gnosys.json");
626
- try {
627
- const identityContent = await fs.readFile(identityPath, "utf-8");
628
- const identity = JSON.parse(identityContent);
629
- const targetMap = {
630
- cursor: ".cursor/rules/gnosys.mdc",
631
- claude: "CLAUDE.md",
632
- codex: "CODEX.md",
633
- };
634
- identity.agentRulesTarget = targetMap[normalizedIde] || null;
635
- await fs.writeFile(identityPath, JSON.stringify(identity, null, 2) + "\n", "utf-8");
636
- console.log(` Config: agentRulesTarget ${identity.agentRulesTarget}`);
637
- }
638
- catch {
639
- // Non-critical
683
+ // Update agentRulesTarget in gnosys.json (only for IDEs with rules files)
684
+ const targetMap = {
685
+ cursor: ".cursor/rules/gnosys.mdc",
686
+ claude: "CLAUDE.md",
687
+ codex: "CODEX.md",
688
+ };
689
+ if (targetMap[normalizedIde]) {
690
+ const identityPath = path.join(storePath, "gnosys.json");
691
+ try {
692
+ const identityContent = await fs.readFile(identityPath, "utf-8");
693
+ const identity = JSON.parse(identityContent);
694
+ identity.agentRulesTarget = targetMap[normalizedIde];
695
+ await fs.writeFile(identityPath, JSON.stringify(identity, null, 2) + "\n", "utf-8");
696
+ console.log(` Config: agentRulesTarget → ${identity.agentRulesTarget}`);
697
+ }
698
+ catch {
699
+ // Non-critical
700
+ }
640
701
  }
641
702
  }
642
703
  }
@@ -3103,7 +3164,7 @@ program
3103
3164
  .action(async (opts) => {
3104
3165
  const projectDir = opts.directory ? path.resolve(opts.directory) : process.cwd();
3105
3166
  const storePath = path.join(projectDir, ".gnosys");
3106
- const globalStorePath = path.join(os.homedir(), ".gnosys");
3167
+ const globalStorePath = getGnosysHome();
3107
3168
  // Load config: try project-level first, fall back to global ~/.gnosys/
3108
3169
  let cfg;
3109
3170
  let configSource;
@@ -3685,10 +3746,22 @@ program
3685
3746
  console.log(` Central DB: ${GnosysDB.getCentralDbPath()}`);
3686
3747
  });
3687
3748
  // ─── gnosys projects ────────────────────────────────────────────────────
3749
+ /**
3750
+ * Returns true if a project's working directory no longer exists on disk.
3751
+ * Used by `gnosys projects` to filter dead entries by default and by
3752
+ * `gnosys projects --prune` to delete them. We deliberately do NOT pattern-
3753
+ * match on tmp paths — active test fixtures live in /var/folders/ and
3754
+ * /tmp/ and we want them visible while they're in use.
3755
+ */
3756
+ function isDeadProjectDir(dir) {
3757
+ return !existsSync(dir);
3758
+ }
3688
3759
  program
3689
3760
  .command("projects")
3690
- .description("List all registered projects in the central DB")
3761
+ .description("List registered projects from the central DB")
3691
3762
  .option("--json", "Output as JSON")
3763
+ .option("--all", "Include dead projects (deleted directories, /tmp/ paths)")
3764
+ .option("--prune", "Delete registry entries whose directory no longer exists or is a tmp path")
3692
3765
  .action(async (opts) => {
3693
3766
  let centralDb = null;
3694
3767
  try {
@@ -3697,18 +3770,71 @@ program
3697
3770
  console.error("Central DB not available (better-sqlite3 missing).");
3698
3771
  process.exit(1);
3699
3772
  }
3700
- const projects = centralDb.getAllProjects();
3701
- if (projects.length === 0) {
3702
- console.log("No projects registered. Run 'gnosys init' in a project directory.");
3773
+ const allProjects = centralDb.getAllProjects();
3774
+ if (opts.prune) {
3775
+ // Find and delete dead projects
3776
+ const deadProjects = allProjects.filter((p) => isDeadProjectDir(p.working_directory));
3777
+ for (const p of deadProjects) {
3778
+ centralDb.deleteProject(p.id);
3779
+ }
3780
+ outputResult(!!opts.json, {
3781
+ deleted: deadProjects.length,
3782
+ remaining: allProjects.length - deadProjects.length,
3783
+ deletedProjects: deadProjects.map((p) => ({ id: p.id, name: p.name, directory: p.working_directory })),
3784
+ }, () => {
3785
+ if (deadProjects.length === 0) {
3786
+ console.log("No dead projects to prune.");
3787
+ }
3788
+ else {
3789
+ const DIM = "\x1b[2m";
3790
+ const RESET = "\x1b[0m";
3791
+ console.log(`Pruned ${deadProjects.length} dead project(s):\n`);
3792
+ for (const p of deadProjects) {
3793
+ console.log(` ${p.name} ${DIM}${p.working_directory}${RESET}`);
3794
+ }
3795
+ console.log();
3796
+ console.log(`${allProjects.length - deadProjects.length} project(s) remain.`);
3797
+ }
3798
+ });
3799
+ return;
3800
+ }
3801
+ // Normal listing — filter dead projects by default
3802
+ const visibleProjects = opts.all
3803
+ ? allProjects
3804
+ : allProjects.filter((p) => !isDeadProjectDir(p.working_directory));
3805
+ if (visibleProjects.length === 0) {
3806
+ const deadCount = allProjects.length;
3807
+ outputResult(!!opts.json, {
3808
+ count: 0,
3809
+ totalRegistered: deadCount,
3810
+ deadCount,
3811
+ projects: [],
3812
+ }, () => {
3813
+ if (deadCount === 0) {
3814
+ console.log("No projects registered. Run 'gnosys init' in a project directory.");
3815
+ }
3816
+ else {
3817
+ console.log(`No live projects (${deadCount} dead — run 'gnosys projects --all' to see them or 'gnosys projects --prune' to remove them).`);
3818
+ }
3819
+ });
3703
3820
  centralDb.close();
3704
3821
  return;
3705
3822
  }
3706
- const projectData = projects.map((p) => ({
3823
+ const projectData = visibleProjects.map((p) => ({
3707
3824
  ...p,
3708
3825
  memoryCount: centralDb.getMemoriesByProject(p.id).length,
3709
3826
  }));
3710
- outputResult(!!opts.json, { count: projects.length, projects: projectData }, () => {
3711
- console.log(`${projects.length} registered project(s):\n`);
3827
+ const deadCount = allProjects.length - visibleProjects.length;
3828
+ outputResult(!!opts.json, {
3829
+ count: visibleProjects.length,
3830
+ totalRegistered: allProjects.length,
3831
+ deadCount,
3832
+ projects: projectData,
3833
+ }, () => {
3834
+ const header = deadCount > 0 && !opts.all
3835
+ ? `${visibleProjects.length} live project(s) (${deadCount} dead hidden — use --all or --prune):\n`
3836
+ : `${visibleProjects.length} registered project(s):\n`;
3837
+ console.log(header);
3712
3838
  for (const p of projectData) {
3713
3839
  console.log(` ${p.name}`);
3714
3840
  console.log(` ID: ${p.id}`);