gnosys 5.5.0 → 5.7.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 +44 -0
- package/dist/cli.js +959 -438
- package/dist/cli.js.map +1 -1
- package/dist/lib/audit.d.ts +13 -0
- package/dist/lib/audit.d.ts.map +1 -1
- package/dist/lib/audit.js +42 -0
- package/dist/lib/audit.js.map +1 -1
- package/dist/lib/chat/choose.d.ts +75 -0
- package/dist/lib/chat/choose.d.ts.map +1 -0
- package/dist/lib/chat/choose.js +146 -0
- package/dist/lib/chat/choose.js.map +1 -0
- package/dist/lib/chat/commands.d.ts +96 -0
- package/dist/lib/chat/commands.d.ts.map +1 -0
- package/dist/lib/chat/commands.js +367 -0
- package/dist/lib/chat/commands.js.map +1 -0
- package/dist/lib/chat/focus.d.ts +70 -0
- package/dist/lib/chat/focus.d.ts.map +1 -0
- package/dist/lib/chat/focus.js +120 -0
- package/dist/lib/chat/focus.js.map +1 -0
- package/dist/lib/chat/index.d.ts +32 -0
- package/dist/lib/chat/index.d.ts.map +1 -0
- package/dist/lib/chat/index.js +151 -0
- package/dist/lib/chat/index.js.map +1 -0
- package/dist/lib/chat/intent.d.ts +100 -0
- package/dist/lib/chat/intent.d.ts.map +1 -0
- package/dist/lib/chat/intent.js +192 -0
- package/dist/lib/chat/intent.js.map +1 -0
- package/dist/lib/chat/llmTurn.d.ts +55 -0
- package/dist/lib/chat/llmTurn.d.ts.map +1 -0
- package/dist/lib/chat/llmTurn.js +103 -0
- package/dist/lib/chat/llmTurn.js.map +1 -0
- package/dist/lib/chat/recall.d.ts +58 -0
- package/dist/lib/chat/recall.d.ts.map +1 -0
- package/dist/lib/chat/recall.js +109 -0
- package/dist/lib/chat/recall.js.map +1 -0
- package/dist/lib/chat/render.d.ts +30 -0
- package/dist/lib/chat/render.d.ts.map +1 -0
- package/dist/lib/chat/render.js +755 -0
- package/dist/lib/chat/render.js.map +1 -0
- package/dist/lib/chat/session.d.ts +121 -0
- package/dist/lib/chat/session.d.ts.map +1 -0
- package/dist/lib/chat/session.js +148 -0
- package/dist/lib/chat/session.js.map +1 -0
- package/dist/lib/chat/toolFence.d.ts +54 -0
- package/dist/lib/chat/toolFence.d.ts.map +1 -0
- package/dist/lib/chat/toolFence.js +90 -0
- package/dist/lib/chat/toolFence.js.map +1 -0
- package/dist/lib/chat/tools.d.ts +48 -0
- package/dist/lib/chat/tools.d.ts.map +1 -0
- package/dist/lib/chat/tools.js +338 -0
- package/dist/lib/chat/tools.js.map +1 -0
- package/dist/lib/chat/types.d.ts +42 -0
- package/dist/lib/chat/types.d.ts.map +1 -0
- package/dist/lib/chat/types.js +6 -0
- package/dist/lib/chat/types.js.map +1 -0
- package/dist/lib/chat/write.d.ts +66 -0
- package/dist/lib/chat/write.d.ts.map +1 -0
- package/dist/lib/chat/write.js +203 -0
- package/dist/lib/chat/write.js.map +1 -0
- package/dist/lib/db.d.ts +41 -1
- package/dist/lib/db.d.ts.map +1 -1
- package/dist/lib/db.js +136 -28
- package/dist/lib/db.js.map +1 -1
- package/dist/lib/exportProject.d.ts +51 -0
- package/dist/lib/exportProject.d.ts.map +1 -0
- package/dist/lib/exportProject.js +72 -0
- package/dist/lib/exportProject.js.map +1 -0
- package/dist/lib/importProject.d.ts +35 -0
- package/dist/lib/importProject.d.ts.map +1 -0
- package/dist/lib/importProject.js +135 -0
- package/dist/lib/importProject.js.map +1 -0
- package/dist/lib/remote.d.ts +23 -0
- package/dist/lib/remote.d.ts.map +1 -1
- package/dist/lib/remote.js +88 -0
- package/dist/lib/remote.js.map +1 -1
- package/dist/lib/remoteWizard.d.ts.map +1 -1
- package/dist/lib/remoteWizard.js +13 -17
- package/dist/lib/remoteWizard.js.map +1 -1
- package/dist/lib/setup/sections/ides.d.ts +20 -0
- package/dist/lib/setup/sections/ides.d.ts.map +1 -0
- package/dist/lib/setup/sections/ides.js +124 -0
- package/dist/lib/setup/sections/ides.js.map +1 -0
- package/dist/lib/setup/sections/preferences.d.ts +30 -0
- package/dist/lib/setup/sections/preferences.d.ts.map +1 -0
- package/dist/lib/setup/sections/preferences.js +128 -0
- package/dist/lib/setup/sections/preferences.js.map +1 -0
- package/dist/lib/setup/sections/routing.d.ts +21 -0
- package/dist/lib/setup/sections/routing.d.ts.map +1 -0
- package/dist/lib/setup/sections/routing.js +160 -0
- package/dist/lib/setup/sections/routing.js.map +1 -0
- package/dist/lib/setup/summary.d.ts +42 -0
- package/dist/lib/setup/summary.d.ts.map +1 -0
- package/dist/lib/setup/summary.js +206 -0
- package/dist/lib/setup/summary.js.map +1 -0
- package/dist/lib/timeline.d.ts +7 -0
- package/dist/lib/timeline.d.ts.map +1 -1
- package/dist/lib/timeline.js +19 -5
- package/dist/lib/timeline.js.map +1 -1
- package/package.json +7 -1
package/dist/cli.js
CHANGED
|
@@ -17,7 +17,7 @@ import { GnosysTagRegistry } from "./lib/tags.js";
|
|
|
17
17
|
import { GnosysIngestion } from "./lib/ingest.js";
|
|
18
18
|
import { applyLens } from "./lib/lensing.js";
|
|
19
19
|
import { getFileHistory, rollbackToCommit, hasGitHistory, getFileDiff } from "./lib/history.js";
|
|
20
|
-
import {
|
|
20
|
+
import { computeStats } from "./lib/timeline.js";
|
|
21
21
|
import { buildLinkGraph, getBacklinks, getOutgoingLinks, formatGraphSummary } from "./lib/wikilinks.js";
|
|
22
22
|
import { bootstrap, discoverFiles } from "./lib/bootstrap.js";
|
|
23
23
|
import { performImport, formatImportSummary } from "./lib/import.js";
|
|
@@ -108,6 +108,32 @@ function maybePrintUpgradeNudge() {
|
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
110
|
maybePrintUpgradeNudge();
|
|
111
|
+
/**
|
|
112
|
+
* v5.6.0 back-compat shim: rewrite `gnosys export --to <dir>` →
|
|
113
|
+
* `gnosys export vault --to <dir>` before commander parses argv. The v5.6.0
|
|
114
|
+
* restructure made `export` a parent command with `vault` and `project`
|
|
115
|
+
* subcommands; without this shim, the bare `--to` form prints usage instead
|
|
116
|
+
* of running the vault export.
|
|
117
|
+
*
|
|
118
|
+
* Pattern: argv[2]==="export" AND argv[3] is not a known subcommand AND any
|
|
119
|
+
* of the v5.5.x flags appear (`--to`, `--all`, `--overwrite`, etc.).
|
|
120
|
+
*/
|
|
121
|
+
function rewriteLegacyExport() {
|
|
122
|
+
if (process.argv[2] !== "export")
|
|
123
|
+
return;
|
|
124
|
+
const next = process.argv[3];
|
|
125
|
+
if (next === "vault" || next === "project" || next === "--help" || next === "-h")
|
|
126
|
+
return;
|
|
127
|
+
// Any v5.5.x-style flag → assume legacy vault invocation
|
|
128
|
+
const looksLegacy = process.argv.slice(3).some((a) => a === "--to" || a.startsWith("--to=") ||
|
|
129
|
+
a === "--all" || a === "--overwrite" ||
|
|
130
|
+
a === "--no-summaries" || a === "--no-reviews" || a === "--no-graph" ||
|
|
131
|
+
a === "--json");
|
|
132
|
+
if (looksLegacy) {
|
|
133
|
+
process.argv.splice(3, 0, "vault");
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
rewriteLegacyExport();
|
|
111
137
|
async function getResolver() {
|
|
112
138
|
const resolver = new GnosysResolver();
|
|
113
139
|
await resolver.resolve();
|
|
@@ -137,6 +163,23 @@ program
|
|
|
137
163
|
.name("gnosys")
|
|
138
164
|
.description("Gnosys — Persistent memory for AI agents. Sandbox-first runtime, central SQLite brain, federated search, reflection API, process tracing, preferences, Dream Mode, Obsidian export. Also runs as a full MCP server.")
|
|
139
165
|
.version(pkg.version)
|
|
166
|
+
.addHelpText("after", `
|
|
167
|
+
Commands by group (alphabetical within group):
|
|
168
|
+
Setup & status: setup · status · doctor · check · upgrade
|
|
169
|
+
Memory ops: add · add-structured · update · read · reinforce · ingest
|
|
170
|
+
bootstrap · import · export
|
|
171
|
+
Search: discover · search · hybrid-search · semantic-search · ask · recall
|
|
172
|
+
fsearch · briefing · lens
|
|
173
|
+
Project mgmt: init · projects · list · stats · timeline · graph · tags · tags-add
|
|
174
|
+
stale · history · rollback · audit · links
|
|
175
|
+
Chat (TUI): chat
|
|
176
|
+
Maintenance: maintain · reindex · reindex-graph · dearchive · dream · backup · restore · prune
|
|
177
|
+
Multi-machine: setup remote (configure | status | push | pull | sync | resolve)
|
|
178
|
+
Agent runtime: serve · sandbox · helper · pref · sync · update-status · working-set
|
|
179
|
+
Legacy / advanced: dashboard · migrate · migrate-db · stores · config
|
|
180
|
+
|
|
181
|
+
Run 'gnosys <command> --help' for command-specific help.
|
|
182
|
+
`)
|
|
140
183
|
.hook("preAction", async () => {
|
|
141
184
|
// Check if central DB was upgraded to a newer version on another machine
|
|
142
185
|
try {
|
|
@@ -517,13 +560,26 @@ program
|
|
|
517
560
|
const setupCmd = program
|
|
518
561
|
.command("setup")
|
|
519
562
|
.description("Configure Gnosys — LLM provider, models, remote sync, and IDE integration");
|
|
520
|
-
// Bare `gnosys setup`
|
|
563
|
+
// Bare `gnosys setup` — when config exists, opens the summary-first menu
|
|
564
|
+
// so the user can edit one section without re-running the whole wizard.
|
|
565
|
+
// First-time setup or `--full` runs the linear 5-step flow.
|
|
521
566
|
setupCmd
|
|
522
567
|
.option("--non-interactive", "Skip prompts, use defaults (for CI/scripting)")
|
|
568
|
+
.option("--full", "Run the linear 5-step wizard even when a config exists")
|
|
523
569
|
.action(async (opts) => {
|
|
524
570
|
const { runSetup } = await import("./lib/setup.js");
|
|
571
|
+
const projectDir = process.cwd();
|
|
572
|
+
// Detect existing config — if present and the user didn't pass --full,
|
|
573
|
+
// route to the summary-first menu.
|
|
574
|
+
const configPath = path.join(os.homedir(), ".gnosys", "gnosys.json");
|
|
575
|
+
const hasConfig = existsSync(configPath);
|
|
576
|
+
if (hasConfig && !opts.full && !opts.nonInteractive) {
|
|
577
|
+
const { runSummaryWizard } = await import("./lib/setup/summary.js");
|
|
578
|
+
await runSummaryWizard({ directory: projectDir });
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
525
581
|
await runSetup({
|
|
526
|
-
directory:
|
|
582
|
+
directory: projectDir,
|
|
527
583
|
nonInteractive: opts.nonInteractive,
|
|
528
584
|
});
|
|
529
585
|
});
|
|
@@ -543,14 +599,17 @@ setupCmd
|
|
|
543
599
|
validate: opts.validate,
|
|
544
600
|
});
|
|
545
601
|
});
|
|
546
|
-
//
|
|
547
|
-
|
|
602
|
+
// ─── gnosys setup remote (parent + subcommands) ────────────────────────
|
|
603
|
+
// v5.7.0: the standalone `gnosys remote` parent was dropped; everything
|
|
604
|
+
// (configure, status, push, pull, sync, resolve) lives here now.
|
|
605
|
+
const setupRemoteCmd = setupCmd
|
|
548
606
|
.command("remote")
|
|
549
|
-
.description("
|
|
607
|
+
.description("Multi-machine sync — configure, sync, and resolve conflicts");
|
|
608
|
+
// Bare `gnosys setup remote` — configure wizard (back-compat with v5.6.x)
|
|
609
|
+
setupRemoteCmd
|
|
550
610
|
.option("--path <path>", "Set remote path directly (non-interactive)")
|
|
551
611
|
.action(async (opts) => {
|
|
552
612
|
const { GnosysDB } = await import("./lib/db.js");
|
|
553
|
-
// Sync configuration needs explicit local DB access (not auto-routed remote).
|
|
554
613
|
const db = GnosysDB.openLocal();
|
|
555
614
|
if (!db.isAvailable()) {
|
|
556
615
|
console.error("Central DB not available.");
|
|
@@ -571,6 +630,233 @@ setupCmd
|
|
|
571
630
|
db.close();
|
|
572
631
|
}
|
|
573
632
|
});
|
|
633
|
+
setupRemoteCmd
|
|
634
|
+
.command("status")
|
|
635
|
+
.description("Show remote sync status: pending changes, conflicts, last sync")
|
|
636
|
+
.option("--json", "Output as JSON")
|
|
637
|
+
.action(async (opts) => {
|
|
638
|
+
let centralDb = null;
|
|
639
|
+
try {
|
|
640
|
+
centralDb = GnosysDB.openLocal();
|
|
641
|
+
if (!centralDb.isAvailable()) {
|
|
642
|
+
console.error("Central DB not available.");
|
|
643
|
+
process.exit(1);
|
|
644
|
+
}
|
|
645
|
+
const remotePath = centralDb.getMeta("remote_path");
|
|
646
|
+
if (!remotePath) {
|
|
647
|
+
if (opts.json) {
|
|
648
|
+
console.log(JSON.stringify({ configured: false, message: "Remote not configured. Run 'gnosys setup remote'." }, null, 2));
|
|
649
|
+
}
|
|
650
|
+
else {
|
|
651
|
+
console.log("Remote sync: not configured.");
|
|
652
|
+
console.log("Run 'gnosys setup remote' to set up multi-machine sync.");
|
|
653
|
+
}
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
const { RemoteSync, formatStatus } = await import("./lib/remote.js");
|
|
657
|
+
const sync = new RemoteSync(centralDb, remotePath);
|
|
658
|
+
const status = await sync.getStatus();
|
|
659
|
+
sync.closeRemote();
|
|
660
|
+
if (opts.json) {
|
|
661
|
+
console.log(JSON.stringify(status, null, 2));
|
|
662
|
+
}
|
|
663
|
+
else {
|
|
664
|
+
console.log(formatStatus(status));
|
|
665
|
+
if (status.conflicts.length > 0) {
|
|
666
|
+
console.log("\nConflicts:");
|
|
667
|
+
for (const c of status.conflicts) {
|
|
668
|
+
console.log(` ${c.memoryId}: ${c.title}`);
|
|
669
|
+
console.log(` local: ${c.localModified}`);
|
|
670
|
+
console.log(` remote: ${c.remoteModified}`);
|
|
671
|
+
}
|
|
672
|
+
console.log("\nResolve with: gnosys setup remote resolve <memory-id> --keep <local|remote>");
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
catch (err) {
|
|
677
|
+
console.error(`Error: ${err instanceof Error ? err.message : err}`);
|
|
678
|
+
process.exit(1);
|
|
679
|
+
}
|
|
680
|
+
finally {
|
|
681
|
+
centralDb?.close();
|
|
682
|
+
}
|
|
683
|
+
});
|
|
684
|
+
setupRemoteCmd
|
|
685
|
+
.command("push")
|
|
686
|
+
.description("Push local changes to remote")
|
|
687
|
+
.option("--newer-wins", "Auto-resolve conflicts by taking the newer version")
|
|
688
|
+
.action(async (opts) => {
|
|
689
|
+
let centralDb = null;
|
|
690
|
+
try {
|
|
691
|
+
centralDb = GnosysDB.openLocal();
|
|
692
|
+
if (!centralDb.isAvailable()) {
|
|
693
|
+
console.error("Central DB not available.");
|
|
694
|
+
process.exit(1);
|
|
695
|
+
}
|
|
696
|
+
const remotePath = centralDb.getMeta("remote_path");
|
|
697
|
+
if (!remotePath) {
|
|
698
|
+
console.error("Remote not configured.");
|
|
699
|
+
process.exit(1);
|
|
700
|
+
}
|
|
701
|
+
const { RemoteSync } = await import("./lib/remote.js");
|
|
702
|
+
const sync = new RemoteSync(centralDb, remotePath);
|
|
703
|
+
const result = await sync.push({ strategy: opts.newerWins ? "newer-wins" : "skip-and-flag" });
|
|
704
|
+
sync.closeRemote();
|
|
705
|
+
const projParts = (result.projectsPushed || 0) > 0 ? ` | Projects pushed: ${result.projectsPushed}` : "";
|
|
706
|
+
const auditParts = (result.auditPushed || 0) > 0 ? ` | Audit pushed: ${result.auditPushed}` : "";
|
|
707
|
+
console.log(`Pushed: ${result.pushed} | Skipped: ${result.skipped} | Conflicts: ${result.conflicts.length}${projParts}${auditParts}`);
|
|
708
|
+
if (result.errors.length > 0) {
|
|
709
|
+
console.log("\nErrors:");
|
|
710
|
+
for (const e of result.errors)
|
|
711
|
+
console.log(` ${e}`);
|
|
712
|
+
}
|
|
713
|
+
if (result.conflicts.length > 0) {
|
|
714
|
+
console.log("\nConflicts flagged (run 'gnosys setup remote status' for details):");
|
|
715
|
+
for (const c of result.conflicts)
|
|
716
|
+
console.log(` ${c.memoryId} — ${c.title}`);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
catch (err) {
|
|
720
|
+
console.error(`Error: ${err instanceof Error ? err.message : err}`);
|
|
721
|
+
process.exit(1);
|
|
722
|
+
}
|
|
723
|
+
finally {
|
|
724
|
+
centralDb?.close();
|
|
725
|
+
}
|
|
726
|
+
});
|
|
727
|
+
setupRemoteCmd
|
|
728
|
+
.command("pull")
|
|
729
|
+
.description("Pull remote changes to local")
|
|
730
|
+
.option("--newer-wins", "Auto-resolve conflicts by taking the newer version")
|
|
731
|
+
.action(async (opts) => {
|
|
732
|
+
let centralDb = null;
|
|
733
|
+
try {
|
|
734
|
+
centralDb = GnosysDB.openLocal();
|
|
735
|
+
if (!centralDb.isAvailable()) {
|
|
736
|
+
console.error("Central DB not available.");
|
|
737
|
+
process.exit(1);
|
|
738
|
+
}
|
|
739
|
+
const remotePath = centralDb.getMeta("remote_path");
|
|
740
|
+
if (!remotePath) {
|
|
741
|
+
console.error("Remote not configured.");
|
|
742
|
+
process.exit(1);
|
|
743
|
+
}
|
|
744
|
+
const { RemoteSync } = await import("./lib/remote.js");
|
|
745
|
+
const sync = new RemoteSync(centralDb, remotePath);
|
|
746
|
+
const result = await sync.pull({ strategy: opts.newerWins ? "newer-wins" : "skip-and-flag" });
|
|
747
|
+
sync.closeRemote();
|
|
748
|
+
const projParts = (result.projectsPulled || 0) > 0 ? ` | Projects pulled: ${result.projectsPulled}` : "";
|
|
749
|
+
const auditParts = (result.auditPulled || 0) > 0 ? ` | Audit pulled: ${result.auditPulled}` : "";
|
|
750
|
+
console.log(`Pulled: ${result.pulled} | Skipped: ${result.skipped} | Conflicts: ${result.conflicts.length}${projParts}${auditParts}`);
|
|
751
|
+
if (result.errors.length > 0) {
|
|
752
|
+
console.log("\nErrors:");
|
|
753
|
+
for (const e of result.errors)
|
|
754
|
+
console.log(` ${e}`);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
catch (err) {
|
|
758
|
+
console.error(`Error: ${err instanceof Error ? err.message : err}`);
|
|
759
|
+
process.exit(1);
|
|
760
|
+
}
|
|
761
|
+
finally {
|
|
762
|
+
centralDb?.close();
|
|
763
|
+
}
|
|
764
|
+
});
|
|
765
|
+
setupRemoteCmd
|
|
766
|
+
.command("sync")
|
|
767
|
+
.description("Two-way sync: push local changes then pull remote changes")
|
|
768
|
+
.option("--auto", "Run silently for cron/LaunchAgent (skip-and-flag for conflicts)")
|
|
769
|
+
.option("--newer-wins", "Auto-resolve conflicts by taking the newer version")
|
|
770
|
+
.action(async (opts) => {
|
|
771
|
+
let centralDb = null;
|
|
772
|
+
try {
|
|
773
|
+
centralDb = GnosysDB.openLocal();
|
|
774
|
+
if (!centralDb.isAvailable()) {
|
|
775
|
+
if (!opts.auto)
|
|
776
|
+
console.error("Central DB not available.");
|
|
777
|
+
process.exit(1);
|
|
778
|
+
}
|
|
779
|
+
const remotePath = centralDb.getMeta("remote_path");
|
|
780
|
+
if (!remotePath) {
|
|
781
|
+
if (!opts.auto)
|
|
782
|
+
console.error("Remote not configured.");
|
|
783
|
+
process.exit(opts.auto ? 0 : 1);
|
|
784
|
+
}
|
|
785
|
+
const { RemoteSync } = await import("./lib/remote.js");
|
|
786
|
+
const sync = new RemoteSync(centralDb, remotePath);
|
|
787
|
+
const result = await sync.sync({
|
|
788
|
+
auto: opts.auto,
|
|
789
|
+
strategy: opts.newerWins ? "newer-wins" : "skip-and-flag",
|
|
790
|
+
});
|
|
791
|
+
sync.closeRemote();
|
|
792
|
+
if (!opts.auto || result.conflicts.length > 0 || result.errors.length > 0) {
|
|
793
|
+
const pp = result.projectsPushed || 0;
|
|
794
|
+
const pl = result.projectsPulled || 0;
|
|
795
|
+
const ap = result.auditPushed || 0;
|
|
796
|
+
const al = result.auditPulled || 0;
|
|
797
|
+
const projParts = (pp + pl) > 0 ? ` | Projects: ↑${pp}/↓${pl}` : "";
|
|
798
|
+
const auditParts = (ap + al) > 0 ? ` | Audit: ↑${ap}/↓${al}` : "";
|
|
799
|
+
console.log(`Pushed: ${result.pushed} | Pulled: ${result.pulled} | Conflicts: ${result.conflicts.length}${projParts}${auditParts}`);
|
|
800
|
+
if (result.errors.length > 0) {
|
|
801
|
+
console.log("\nErrors:");
|
|
802
|
+
for (const e of result.errors)
|
|
803
|
+
console.log(` ${e}`);
|
|
804
|
+
}
|
|
805
|
+
if (result.conflicts.length > 0) {
|
|
806
|
+
console.log("\nConflicts need resolution (run 'gnosys setup remote status' for details).");
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
catch (err) {
|
|
811
|
+
if (!opts.auto)
|
|
812
|
+
console.error(`Error: ${err instanceof Error ? err.message : err}`);
|
|
813
|
+
process.exit(1);
|
|
814
|
+
}
|
|
815
|
+
finally {
|
|
816
|
+
centralDb?.close();
|
|
817
|
+
}
|
|
818
|
+
});
|
|
819
|
+
setupRemoteCmd
|
|
820
|
+
.command("resolve <memoryId>")
|
|
821
|
+
.description("Resolve a sync conflict by choosing local, remote, or merged content")
|
|
822
|
+
.option("--keep <choice>", "Choice: local | remote", "local")
|
|
823
|
+
.action(async (memoryId, opts) => {
|
|
824
|
+
let centralDb = null;
|
|
825
|
+
try {
|
|
826
|
+
centralDb = GnosysDB.openLocal();
|
|
827
|
+
if (!centralDb.isAvailable()) {
|
|
828
|
+
console.error("Central DB not available.");
|
|
829
|
+
process.exit(1);
|
|
830
|
+
}
|
|
831
|
+
const remotePath = centralDb.getMeta("remote_path");
|
|
832
|
+
if (!remotePath) {
|
|
833
|
+
console.error("Remote not configured.");
|
|
834
|
+
process.exit(1);
|
|
835
|
+
}
|
|
836
|
+
if (opts.keep !== "local" && opts.keep !== "remote") {
|
|
837
|
+
console.error(`--keep must be 'local' or 'remote' (got: ${opts.keep})`);
|
|
838
|
+
process.exit(1);
|
|
839
|
+
}
|
|
840
|
+
const { RemoteSync } = await import("./lib/remote.js");
|
|
841
|
+
const sync = new RemoteSync(centralDb, remotePath);
|
|
842
|
+
const result = await sync.resolve(memoryId, opts.keep);
|
|
843
|
+
sync.closeRemote();
|
|
844
|
+
if (result.ok) {
|
|
845
|
+
console.log(`Resolved ${memoryId}: kept ${opts.keep} version.`);
|
|
846
|
+
}
|
|
847
|
+
else {
|
|
848
|
+
console.error(`Failed to resolve: ${result.error}`);
|
|
849
|
+
process.exit(1);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
catch (err) {
|
|
853
|
+
console.error(`Error: ${err instanceof Error ? err.message : err}`);
|
|
854
|
+
process.exit(1);
|
|
855
|
+
}
|
|
856
|
+
finally {
|
|
857
|
+
centralDb?.close();
|
|
858
|
+
}
|
|
859
|
+
});
|
|
574
860
|
// `gnosys setup dream` — configure dream mode (designation, provider, schedule)
|
|
575
861
|
setupCmd
|
|
576
862
|
.command("dream")
|
|
@@ -579,6 +865,51 @@ setupCmd
|
|
|
579
865
|
const { runDreamSetup } = await import("./lib/setup.js");
|
|
580
866
|
await runDreamSetup({ directory: process.cwd() });
|
|
581
867
|
});
|
|
868
|
+
// `gnosys setup ides` — configure IDE / MCP integrations standalone
|
|
869
|
+
setupCmd
|
|
870
|
+
.command("ides")
|
|
871
|
+
.description("Configure IDE integrations (Claude Code/Desktop, Cursor, Codex, Gemini CLI, Antigravity)")
|
|
872
|
+
.action(async () => {
|
|
873
|
+
const readline = await import("readline/promises");
|
|
874
|
+
const { runIdesSetup } = await import("./lib/setup/sections/ides.js");
|
|
875
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
876
|
+
try {
|
|
877
|
+
await runIdesSetup({ rl, directory: process.cwd() });
|
|
878
|
+
}
|
|
879
|
+
finally {
|
|
880
|
+
rl.close();
|
|
881
|
+
}
|
|
882
|
+
});
|
|
883
|
+
// `gnosys setup routing` — task-routing wizard standalone
|
|
884
|
+
setupCmd
|
|
885
|
+
.command("routing")
|
|
886
|
+
.description("Configure per-task LLM routing (structuring, synthesis, vision, transcription, dream)")
|
|
887
|
+
.action(async () => {
|
|
888
|
+
const readline = await import("readline/promises");
|
|
889
|
+
const { runRoutingSetup } = await import("./lib/setup/sections/routing.js");
|
|
890
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
891
|
+
try {
|
|
892
|
+
await runRoutingSetup({ rl, directory: process.cwd() });
|
|
893
|
+
}
|
|
894
|
+
finally {
|
|
895
|
+
rl.close();
|
|
896
|
+
}
|
|
897
|
+
});
|
|
898
|
+
// `gnosys setup preferences` — review user-scope preferences
|
|
899
|
+
setupCmd
|
|
900
|
+
.command("preferences")
|
|
901
|
+
.description("Review and clean up user-scope preferences (incl. legacy imports)")
|
|
902
|
+
.action(async () => {
|
|
903
|
+
const readline = await import("readline/promises");
|
|
904
|
+
const { runPreferencesReview } = await import("./lib/setup/sections/preferences.js");
|
|
905
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
906
|
+
try {
|
|
907
|
+
await runPreferencesReview(rl);
|
|
908
|
+
}
|
|
909
|
+
finally {
|
|
910
|
+
rl.close();
|
|
911
|
+
}
|
|
912
|
+
});
|
|
582
913
|
// v5.4.2 removal: `gnosys models` (top-level shortcut) was removed in favor
|
|
583
914
|
// of the canonical `gnosys setup models` form. The implementation function
|
|
584
915
|
// runModelsCommand() in setup.ts is no longer wired but kept for now in case
|
|
@@ -1218,21 +1549,60 @@ program
|
|
|
1218
1549
|
centralDb?.close();
|
|
1219
1550
|
}
|
|
1220
1551
|
});
|
|
1221
|
-
// ─── gnosys
|
|
1552
|
+
// ─── gnosys chat (TUI) ───────────────────────────────────────────────────
|
|
1222
1553
|
program
|
|
1223
|
-
.command("
|
|
1224
|
-
.description("
|
|
1225
|
-
.option("--
|
|
1226
|
-
.option("
|
|
1227
|
-
.option("
|
|
1228
|
-
.option("--
|
|
1229
|
-
.option("--
|
|
1230
|
-
.option("--
|
|
1231
|
-
.
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
if (opts.
|
|
1235
|
-
|
|
1554
|
+
.command("chat")
|
|
1555
|
+
.description("Interactive memory-aware terminal chat (TUI)")
|
|
1556
|
+
.option("--resume <sessionId>", "Resume an existing chat session")
|
|
1557
|
+
.option("--list", "List recent chat sessions and exit")
|
|
1558
|
+
.option("--search <query>", "Full-text search across session logs")
|
|
1559
|
+
.option("--provider <name>", "Override LLM provider (anthropic, openai, groq, ollama, …)")
|
|
1560
|
+
.option("--model <name>", "Override LLM model name")
|
|
1561
|
+
.option("--limit <n>", "Limit for --list / --search (default 20)", "20")
|
|
1562
|
+
.action(async (opts) => {
|
|
1563
|
+
const limit = parseInt(opts.limit, 10) || 20;
|
|
1564
|
+
const chat = await import("./lib/chat/index.js");
|
|
1565
|
+
if (opts.list) {
|
|
1566
|
+
chat.printSessionList(limit);
|
|
1567
|
+
return;
|
|
1568
|
+
}
|
|
1569
|
+
if (opts.search) {
|
|
1570
|
+
chat.printSearchResults(opts.search, limit);
|
|
1571
|
+
return;
|
|
1572
|
+
}
|
|
1573
|
+
// Interactive chat
|
|
1574
|
+
const resolver = await getResolver();
|
|
1575
|
+
const stores = resolver.getStores();
|
|
1576
|
+
const storePath = stores[0]?.path ?? process.cwd();
|
|
1577
|
+
let cliConfig;
|
|
1578
|
+
try {
|
|
1579
|
+
cliConfig = await loadConfig(storePath);
|
|
1580
|
+
}
|
|
1581
|
+
catch {
|
|
1582
|
+
cliConfig = (await import("./lib/config.js")).DEFAULT_CONFIG;
|
|
1583
|
+
}
|
|
1584
|
+
await chat.startChat({
|
|
1585
|
+
config: cliConfig,
|
|
1586
|
+
resume: opts.resume,
|
|
1587
|
+
providerName: opts.provider,
|
|
1588
|
+
modelName: opts.model,
|
|
1589
|
+
});
|
|
1590
|
+
});
|
|
1591
|
+
// ─── gnosys ingest <file> ─────────────────────────────────────────────────
|
|
1592
|
+
program
|
|
1593
|
+
.command("ingest <fileOrGlob>")
|
|
1594
|
+
.description("Ingest a file (PDF, DOCX, TXT, MD) into Gnosys memory. Extracts text, splits into chunks, and creates atomic memories.")
|
|
1595
|
+
.option("--mode <mode>", "Ingestion mode: llm or structured", "llm")
|
|
1596
|
+
.option("-s, --store <store>", "Target store: project, personal, global")
|
|
1597
|
+
.option("-a, --author <author>", "Author", "human")
|
|
1598
|
+
.option("--authority <authority>", "Authority level", "imported")
|
|
1599
|
+
.option("--dry-run", "Preview what would be created without writing")
|
|
1600
|
+
.option("--list-attachments", "List all stored attachments")
|
|
1601
|
+
.option("-d, --directory <dir>", "Project directory")
|
|
1602
|
+
.action(async (fileOrGlob, opts) => {
|
|
1603
|
+
// List attachments mode
|
|
1604
|
+
if (opts.listAttachments) {
|
|
1605
|
+
const { listAttachments } = await import("./lib/attachments.js");
|
|
1236
1606
|
const resolver = await getResolver();
|
|
1237
1607
|
const writeTarget = resolver.getWriteTarget(opts.store || undefined);
|
|
1238
1608
|
if (!writeTarget) {
|
|
@@ -1616,34 +1986,51 @@ program
|
|
|
1616
1986
|
.command("timeline")
|
|
1617
1987
|
.description("Show when memories were created and modified over time")
|
|
1618
1988
|
.option("-p, --period <period>", "Group by: day, week, month (default), year", "month")
|
|
1989
|
+
.option("--project <id>", "Filter to a specific project ID (default: all projects)")
|
|
1990
|
+
.option("--limit-titles <n>", "Show titles inline when an entry has <= N memories (default 5)", "5")
|
|
1619
1991
|
.action(async (opts) => {
|
|
1620
|
-
const
|
|
1621
|
-
const
|
|
1622
|
-
if (
|
|
1623
|
-
console.
|
|
1624
|
-
|
|
1992
|
+
const { groupDbByPeriod } = await import("./lib/timeline.js");
|
|
1993
|
+
const centralDb = GnosysDB.openCentral();
|
|
1994
|
+
if (!centralDb.isAvailable()) {
|
|
1995
|
+
console.error("Central DB unavailable.");
|
|
1996
|
+
process.exit(1);
|
|
1625
1997
|
}
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
if (
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1998
|
+
try {
|
|
1999
|
+
const memories = opts.project
|
|
2000
|
+
? centralDb.getMemoriesByProject(opts.project)
|
|
2001
|
+
: centralDb.getActiveMemories();
|
|
2002
|
+
if (memories.length === 0) {
|
|
2003
|
+
console.log("No memories found.");
|
|
2004
|
+
return;
|
|
2005
|
+
}
|
|
2006
|
+
const entries = groupDbByPeriod(memories, opts.period);
|
|
2007
|
+
const titleLimit = Math.max(0, parseInt(opts.limitTitles, 10) || 5);
|
|
2008
|
+
console.log(`Knowledge Timeline (by ${opts.period}, ${memories.length} memories):\n`);
|
|
2009
|
+
for (const entry of entries) {
|
|
2010
|
+
const parts = [];
|
|
2011
|
+
if (entry.created > 0)
|
|
2012
|
+
parts.push(`${entry.created} created`);
|
|
2013
|
+
if (entry.modified > 0)
|
|
2014
|
+
parts.push(`${entry.modified} modified`);
|
|
2015
|
+
console.log(` ${entry.period}: ${parts.join(", ")}`);
|
|
2016
|
+
if (entry.titles.length > 0 && entry.titles.length <= titleLimit) {
|
|
2017
|
+
for (const t of entry.titles) {
|
|
2018
|
+
console.log(` + ${t}`);
|
|
2019
|
+
}
|
|
1638
2020
|
}
|
|
1639
2021
|
}
|
|
1640
2022
|
}
|
|
2023
|
+
finally {
|
|
2024
|
+
centralDb.close();
|
|
2025
|
+
}
|
|
1641
2026
|
});
|
|
1642
2027
|
// ─── gnosys stats ───────────────────────────────────────────────────────
|
|
1643
2028
|
program
|
|
1644
2029
|
.command("stats")
|
|
1645
|
-
.description("Show summary statistics for the memory store")
|
|
2030
|
+
.description("Show summary statistics for the memory store. Use --by-project for a per-project breakdown across the central DB.")
|
|
1646
2031
|
.option("--json", "Output as JSON")
|
|
2032
|
+
.option("--by-project", "Show a per-project breakdown table instead of single-store stats")
|
|
2033
|
+
.option("--all", "Include all projects (don't filter to current project)")
|
|
1647
2034
|
.action(async (opts) => {
|
|
1648
2035
|
let centralDb = null;
|
|
1649
2036
|
try {
|
|
@@ -1652,8 +2039,66 @@ program
|
|
|
1652
2039
|
console.error("Central DB not available. Run 'gnosys init' first.");
|
|
1653
2040
|
process.exit(1);
|
|
1654
2041
|
}
|
|
2042
|
+
// v5.7.0: --by-project shows a per-project breakdown across the entire
|
|
2043
|
+
// central DB (memories, archived, never reinforced, etc.) as a table.
|
|
2044
|
+
if (opts.byProject) {
|
|
2045
|
+
const projects = centralDb.getAllProjects();
|
|
2046
|
+
const all = centralDb.getAllMemories();
|
|
2047
|
+
const rows = projects.map((p) => {
|
|
2048
|
+
const ms = all.filter((m) => m.project_id === p.id);
|
|
2049
|
+
const active = ms.filter((m) => m.tier === "active" && m.status === "active").length;
|
|
2050
|
+
const archived = ms.filter((m) => m.tier === "archive").length;
|
|
2051
|
+
const reinforced = ms.reduce((sum, m) => sum + (m.reinforcement_count ?? 0), 0);
|
|
2052
|
+
const lastTouch = ms.reduce((m, x) => (x.modified > m ? x.modified : m), "0");
|
|
2053
|
+
return { name: p.name, id: p.id, active, archived, reinforced, lastTouch };
|
|
2054
|
+
});
|
|
2055
|
+
// User/global memories (no project_id)
|
|
2056
|
+
const userScope = all.filter((m) => !m.project_id && m.scope === "user");
|
|
2057
|
+
const globalScope = all.filter((m) => !m.project_id && m.scope === "global");
|
|
2058
|
+
if (userScope.length > 0) {
|
|
2059
|
+
rows.push({
|
|
2060
|
+
name: "(user)",
|
|
2061
|
+
id: "—",
|
|
2062
|
+
active: userScope.filter((m) => m.tier === "active" && m.status === "active").length,
|
|
2063
|
+
archived: userScope.filter((m) => m.tier === "archive").length,
|
|
2064
|
+
reinforced: userScope.reduce((sum, m) => sum + (m.reinforcement_count ?? 0), 0),
|
|
2065
|
+
lastTouch: userScope.reduce((m, x) => (x.modified > m ? x.modified : m), "0"),
|
|
2066
|
+
});
|
|
2067
|
+
}
|
|
2068
|
+
if (globalScope.length > 0) {
|
|
2069
|
+
rows.push({
|
|
2070
|
+
name: "(global)",
|
|
2071
|
+
id: "—",
|
|
2072
|
+
active: globalScope.filter((m) => m.tier === "active" && m.status === "active").length,
|
|
2073
|
+
archived: globalScope.filter((m) => m.tier === "archive").length,
|
|
2074
|
+
reinforced: globalScope.reduce((sum, m) => sum + (m.reinforcement_count ?? 0), 0),
|
|
2075
|
+
lastTouch: globalScope.reduce((m, x) => (x.modified > m ? x.modified : m), "0"),
|
|
2076
|
+
});
|
|
2077
|
+
}
|
|
2078
|
+
rows.sort((a, b) => b.active - a.active);
|
|
2079
|
+
if (opts.json) {
|
|
2080
|
+
console.log(JSON.stringify({ rows }, null, 2));
|
|
2081
|
+
return;
|
|
2082
|
+
}
|
|
2083
|
+
const nameW = Math.max(8, ...rows.map((r) => r.name.length));
|
|
2084
|
+
const idW = 12;
|
|
2085
|
+
console.log("");
|
|
2086
|
+
console.log(` ${"PROJECT".padEnd(nameW)} ${"ID".padEnd(idW)} ${"ACTIVE".padStart(7)} ${"ARCHIVED".padStart(8)} ${"REINF".padStart(6)} LAST MODIFIED`);
|
|
2087
|
+
console.log(` ${"-".repeat(nameW + idW + 7 + 8 + 6 + 19 + 10)}`);
|
|
2088
|
+
for (const r of rows) {
|
|
2089
|
+
const last = r.lastTouch === "0" ? "—" : r.lastTouch.slice(0, 19);
|
|
2090
|
+
const idShort = r.id === "—" ? "—" : r.id.slice(0, idW);
|
|
2091
|
+
console.log(` ${r.name.padEnd(nameW)} ${idShort.padEnd(idW)} ${String(r.active).padStart(7)} ${String(r.archived).padStart(8)} ${String(r.reinforced).padStart(6)} ${last}`);
|
|
2092
|
+
}
|
|
2093
|
+
const totalActive = rows.reduce((s, r) => s + r.active, 0);
|
|
2094
|
+
console.log(` ${"-".repeat(nameW + idW + 7 + 8 + 6 + 19 + 10)}`);
|
|
2095
|
+
console.log(` ${"TOTAL".padEnd(nameW)} ${" ".repeat(idW)} ${String(totalActive).padStart(7)}`);
|
|
2096
|
+
console.log("");
|
|
2097
|
+
return;
|
|
2098
|
+
}
|
|
2099
|
+
// Default behavior: scoped stats (current project + user/global, OR --all)
|
|
1655
2100
|
const projIdentity = await findProjectIdentity(process.cwd());
|
|
1656
|
-
const projectId = projIdentity?.identity.projectId || null;
|
|
2101
|
+
const projectId = !opts.all && projIdentity?.identity.projectId || null;
|
|
1657
2102
|
let dbMemories = centralDb.getActiveMemories();
|
|
1658
2103
|
if (projectId) {
|
|
1659
2104
|
dbMemories = dbMemories.filter((m) => m.project_id === projectId || m.scope === "user" || m.scope === "global");
|
|
@@ -1756,7 +2201,7 @@ program
|
|
|
1756
2201
|
// ─── gnosys graph ───────────────────────────────────────────────────────
|
|
1757
2202
|
program
|
|
1758
2203
|
.command("graph")
|
|
1759
|
-
.description("Show the
|
|
2204
|
+
.description("Show the [[wikilink]] cross-reference graph between memories. Empty until you start using [[Title]] in memory content — then this shows which memories reference each other.")
|
|
1760
2205
|
.action(async () => {
|
|
1761
2206
|
// v5.4.1: Query the central DB directly. Previously this used the
|
|
1762
2207
|
// filesystem resolver, which returns nothing in v5.x DB-only mode
|
|
@@ -1876,12 +2321,13 @@ program
|
|
|
1876
2321
|
}
|
|
1877
2322
|
}
|
|
1878
2323
|
});
|
|
1879
|
-
// ─── gnosys import
|
|
1880
|
-
program
|
|
1881
|
-
.command("import
|
|
1882
|
-
.
|
|
1883
|
-
.
|
|
1884
|
-
.
|
|
2324
|
+
// ─── gnosys import (parent + subcommands) ───────────────────────────────
|
|
2325
|
+
const importCmd = program
|
|
2326
|
+
.command("import [fileOrUrl]")
|
|
2327
|
+
.enablePositionalOptions()
|
|
2328
|
+
.description("Import data into Gnosys (bulk CSV/JSON/JSONL — see also: 'gnosys import project <bundle>')")
|
|
2329
|
+
.option("--format <format>", "Data format: csv, json, jsonl (required for bulk import)")
|
|
2330
|
+
.option("--mapping <json>", 'Field mapping as JSON: \'{"source_field":"gnosys_field"}\'. Valid targets: title, category, content, tags, relevance')
|
|
1885
2331
|
.option("--mode <mode>", "Processing mode: llm or structured", "structured")
|
|
1886
2332
|
.option("--limit <n>", "Max records to import", parseInt)
|
|
1887
2333
|
.option("--offset <n>", "Skip first N records", parseInt)
|
|
@@ -1892,6 +2338,17 @@ program
|
|
|
1892
2338
|
.option("--dry-run", "Preview without writing")
|
|
1893
2339
|
.option("--store <store>", "Target store: project, personal, global", "project")
|
|
1894
2340
|
.action(async (fileOrUrl, opts) => {
|
|
2341
|
+
if (!fileOrUrl) {
|
|
2342
|
+
console.error("Usage:");
|
|
2343
|
+
console.error(" gnosys import <file> --format csv|json|jsonl --mapping '{...}' (bulk)");
|
|
2344
|
+
console.error(" gnosys import project <bundle.json.gz> (project bundle)");
|
|
2345
|
+
process.exit(1);
|
|
2346
|
+
}
|
|
2347
|
+
if (!opts.format || !opts.mapping) {
|
|
2348
|
+
console.error("Error: --format and --mapping are required for bulk imports.");
|
|
2349
|
+
console.error(" For project bundles, use 'gnosys import project <bundle>'.");
|
|
2350
|
+
process.exit(1);
|
|
2351
|
+
}
|
|
1895
2352
|
// Parse mapping JSON
|
|
1896
2353
|
let mapping;
|
|
1897
2354
|
try {
|
|
@@ -1966,10 +2423,55 @@ program
|
|
|
1966
2423
|
process.exit(1);
|
|
1967
2424
|
}
|
|
1968
2425
|
});
|
|
2426
|
+
// `gnosys import project <bundle>` — restore a portable .json.gz bundle
|
|
2427
|
+
importCmd
|
|
2428
|
+
.command("project <bundlePath>")
|
|
2429
|
+
.description("Import a project bundle (.json.gz) created by 'gnosys export project'")
|
|
2430
|
+
.option("--strategy <strategy>", "Conflict handling: merge (default), replace, new-id", "merge")
|
|
2431
|
+
.option("--working-directory <dir>", "Override the bundle's working_directory (e.g. when restoring on a different machine)")
|
|
2432
|
+
.option("--json", "Output the result as JSON")
|
|
2433
|
+
.action(async (bundlePath, opts) => {
|
|
2434
|
+
const validStrategies = ["merge", "replace", "new-id"];
|
|
2435
|
+
if (!validStrategies.includes(opts.strategy)) {
|
|
2436
|
+
console.error(`Invalid strategy: ${opts.strategy}. Use one of: ${validStrategies.join(", ")}`);
|
|
2437
|
+
process.exit(1);
|
|
2438
|
+
}
|
|
2439
|
+
const { GnosysDB: DbClass } = await import("./lib/db.js");
|
|
2440
|
+
const { importProject } = await import("./lib/importProject.js");
|
|
2441
|
+
const centralDb = DbClass.openCentral();
|
|
2442
|
+
if (!centralDb.isAvailable()) {
|
|
2443
|
+
console.error("Central DB unavailable.");
|
|
2444
|
+
process.exit(1);
|
|
2445
|
+
}
|
|
2446
|
+
try {
|
|
2447
|
+
const result = importProject(centralDb, {
|
|
2448
|
+
bundlePath: path.resolve(bundlePath),
|
|
2449
|
+
strategy: opts.strategy,
|
|
2450
|
+
workingDirectoryOverride: opts.workingDirectory,
|
|
2451
|
+
});
|
|
2452
|
+
if (opts.json) {
|
|
2453
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2454
|
+
}
|
|
2455
|
+
else {
|
|
2456
|
+
console.log(`Imported project ${result.projectName} (${result.projectId})`);
|
|
2457
|
+
console.log(` Strategy: ${result.strategy}`);
|
|
2458
|
+
console.log(` Memories: ${result.memoriesInserted} inserted, ${result.memoriesSkipped} skipped, ${result.memoriesReplaced} replaced`);
|
|
2459
|
+
console.log(` Relationships: ${result.relationshipsInserted}`);
|
|
2460
|
+
console.log(` Audit entries: ${result.auditEntriesInserted}`);
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
catch (err) {
|
|
2464
|
+
console.error(`Import failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
2465
|
+
process.exit(1);
|
|
2466
|
+
}
|
|
2467
|
+
finally {
|
|
2468
|
+
centralDb.close();
|
|
2469
|
+
}
|
|
2470
|
+
});
|
|
1969
2471
|
// ─── gnosys reindex ──────────────────────────────────────────────────────
|
|
1970
2472
|
program
|
|
1971
2473
|
.command("reindex")
|
|
1972
|
-
.description("Rebuild
|
|
2474
|
+
.description("Rebuild semantic embeddings for every memory in the central DB. Run after bulk imports, schema changes, or if hybrid search starts returning poor matches. Downloads the all-MiniLM-L6-v2 model (~80 MB) on first run.")
|
|
1973
2475
|
.action(async () => {
|
|
1974
2476
|
const resolver = await getResolver();
|
|
1975
2477
|
const stores = resolver.getStores();
|
|
@@ -2506,9 +3008,11 @@ program
|
|
|
2506
3008
|
console.log(formatGraphStats(stats));
|
|
2507
3009
|
});
|
|
2508
3010
|
// ─── gnosys dashboard ───────────────────────────────────────────────────
|
|
3011
|
+
// v5.7.0: kept as a thin alias of `gnosys status --system`. Will be removed
|
|
3012
|
+
// in a future release; use `gnosys status` instead.
|
|
2509
3013
|
program
|
|
2510
3014
|
.command("dashboard")
|
|
2511
|
-
.description("Show system
|
|
3015
|
+
.description("(alias) Show system health — equivalent to 'gnosys status --system'")
|
|
2512
3016
|
.option("--json", "Output as JSON instead of pretty table")
|
|
2513
3017
|
.action(async (opts) => {
|
|
2514
3018
|
const { collectDashboardData, formatDashboard, formatDashboardJSON } = await import("./lib/dashboard.js");
|
|
@@ -2524,334 +3028,103 @@ program
|
|
|
2524
3028
|
try {
|
|
2525
3029
|
const db = GnosysDB.openCentral();
|
|
2526
3030
|
if (db.isAvailable() && db.isMigrated()) {
|
|
2527
|
-
dashDb = db;
|
|
2528
|
-
}
|
|
2529
|
-
}
|
|
2530
|
-
catch {
|
|
2531
|
-
// Central DB not available — legacy dashboard only
|
|
2532
|
-
}
|
|
2533
|
-
const data = await collectDashboardData(resolver, cfg, pkg.version, dashDb);
|
|
2534
|
-
if (opts.json) {
|
|
2535
|
-
console.log(formatDashboardJSON(data));
|
|
2536
|
-
}
|
|
2537
|
-
else {
|
|
2538
|
-
console.log(formatDashboard(data));
|
|
2539
|
-
}
|
|
2540
|
-
});
|
|
2541
|
-
// ─── gnosys maintain ─────────────────────────────────────────────────────
|
|
2542
|
-
program
|
|
2543
|
-
.command("maintain")
|
|
2544
|
-
.description("Run vault maintenance: detect duplicates, apply confidence decay, consolidate similar memories")
|
|
2545
|
-
.option("--dry-run", "Show what would change without modifying anything")
|
|
2546
|
-
.option("--auto-apply", "Automatically apply all changes (no prompts)")
|
|
2547
|
-
.action(async (opts) => {
|
|
2548
|
-
const { GnosysMaintenanceEngine, formatMaintenanceReport } = await import("./lib/maintenance.js");
|
|
2549
|
-
const resolver = await getResolver();
|
|
2550
|
-
const stores = resolver.getStores();
|
|
2551
|
-
if (stores.length === 0) {
|
|
2552
|
-
console.error("No Gnosys stores found. Run gnosys init first.");
|
|
2553
|
-
process.exit(1);
|
|
2554
|
-
}
|
|
2555
|
-
const cfg = await loadConfig(stores[0].path);
|
|
2556
|
-
const engine = new GnosysMaintenanceEngine(resolver, cfg);
|
|
2557
|
-
const report = await engine.maintain({
|
|
2558
|
-
dryRun: opts.dryRun,
|
|
2559
|
-
autoApply: opts.autoApply,
|
|
2560
|
-
onLog: (level, message) => {
|
|
2561
|
-
if (level === "warn") {
|
|
2562
|
-
console.error(`⚠ ${message}`);
|
|
2563
|
-
}
|
|
2564
|
-
else if (level === "action") {
|
|
2565
|
-
console.log(`→ ${message}`);
|
|
2566
|
-
}
|
|
2567
|
-
else {
|
|
2568
|
-
console.log(message);
|
|
2569
|
-
}
|
|
2570
|
-
},
|
|
2571
|
-
onProgress: (step, current, total) => {
|
|
2572
|
-
process.stdout.write(`\r[${current}/${total}] ${step}...`);
|
|
2573
|
-
if (current === total)
|
|
2574
|
-
process.stdout.write("\n");
|
|
2575
|
-
},
|
|
2576
|
-
});
|
|
2577
|
-
console.log("");
|
|
2578
|
-
console.log(formatMaintenanceReport(report));
|
|
2579
|
-
});
|
|
2580
|
-
// ─── gnosys dearchive ───────────────────────────────────────────────────
|
|
2581
|
-
program
|
|
2582
|
-
.command("dearchive <query>")
|
|
2583
|
-
.description("Force-dearchive memories matching a query from archive.db back to active")
|
|
2584
|
-
.option("--limit <n>", "Max memories to dearchive", "5")
|
|
2585
|
-
.action(async (query, opts) => {
|
|
2586
|
-
const { GnosysArchive } = await import("./lib/archive.js");
|
|
2587
|
-
const resolver = await getResolver();
|
|
2588
|
-
const stores = resolver.getStores();
|
|
2589
|
-
if (stores.length === 0) {
|
|
2590
|
-
console.error("No Gnosys stores found. Run gnosys init first.");
|
|
2591
|
-
process.exit(1);
|
|
2592
|
-
}
|
|
2593
|
-
const writeTarget = resolver.getWriteTarget();
|
|
2594
|
-
if (!writeTarget) {
|
|
2595
|
-
console.error("No writable store found.");
|
|
2596
|
-
process.exit(1);
|
|
2597
|
-
}
|
|
2598
|
-
const archive = new GnosysArchive(writeTarget.path);
|
|
2599
|
-
if (!archive.isAvailable()) {
|
|
2600
|
-
console.error("Archive not available. Is better-sqlite3 installed?");
|
|
2601
|
-
process.exit(1);
|
|
2602
|
-
}
|
|
2603
|
-
const results = archive.searchArchive(query, parseInt(opts.limit));
|
|
2604
|
-
if (results.length === 0) {
|
|
2605
|
-
console.log(`No archived memories found matching "${query}".`);
|
|
2606
|
-
archive.close();
|
|
2607
|
-
return;
|
|
2608
|
-
}
|
|
2609
|
-
console.log(`Found ${results.length} archived memories matching "${query}":\n`);
|
|
2610
|
-
for (const r of results) {
|
|
2611
|
-
console.log(` • ${r.title} (${r.id})`);
|
|
2612
|
-
}
|
|
2613
|
-
console.log("");
|
|
2614
|
-
// Dearchive all found
|
|
2615
|
-
const ids = results.map((r) => r.id);
|
|
2616
|
-
const restored = await archive.dearchiveBatch(ids, writeTarget.store);
|
|
2617
|
-
archive.close();
|
|
2618
|
-
console.log(`Dearchived ${restored.length} memories back to active:`);
|
|
2619
|
-
for (const rp of restored) {
|
|
2620
|
-
console.log(` → ${rp}`);
|
|
2621
|
-
}
|
|
2622
|
-
});
|
|
2623
|
-
// NOTE: gnosys migrate is defined below (near the end) with --to-central support
|
|
2624
|
-
// ─── gnosys remote (multi-machine sync) ────────────────────────────────
|
|
2625
|
-
const remoteCmd = program
|
|
2626
|
-
.command("remote")
|
|
2627
|
-
.description("Multi-machine sync — share gnosys.db across machines via NAS or shared drive");
|
|
2628
|
-
remoteCmd
|
|
2629
|
-
.command("status")
|
|
2630
|
-
.description("Show remote sync status: pending changes, conflicts, last sync")
|
|
2631
|
-
.option("--json", "Output as JSON")
|
|
2632
|
-
.action(async (opts) => {
|
|
2633
|
-
let centralDb = null;
|
|
2634
|
-
try {
|
|
2635
|
-
// Sync operations need explicit local DB access (not auto-routed remote).
|
|
2636
|
-
centralDb = GnosysDB.openLocal();
|
|
2637
|
-
if (!centralDb.isAvailable()) {
|
|
2638
|
-
console.error("Central DB not available.");
|
|
2639
|
-
process.exit(1);
|
|
2640
|
-
}
|
|
2641
|
-
const remotePath = centralDb.getMeta("remote_path");
|
|
2642
|
-
if (!remotePath) {
|
|
2643
|
-
if (opts.json) {
|
|
2644
|
-
console.log(JSON.stringify({ configured: false, message: "Remote not configured. Run 'gnosys remote configure'." }, null, 2));
|
|
2645
|
-
}
|
|
2646
|
-
else {
|
|
2647
|
-
console.log("Remote sync: not configured.");
|
|
2648
|
-
console.log("Run 'gnosys remote configure' to set up multi-machine sync.");
|
|
2649
|
-
}
|
|
2650
|
-
return;
|
|
2651
|
-
}
|
|
2652
|
-
const { RemoteSync, formatStatus } = await import("./lib/remote.js");
|
|
2653
|
-
const sync = new RemoteSync(centralDb, remotePath);
|
|
2654
|
-
const status = await sync.getStatus();
|
|
2655
|
-
sync.closeRemote();
|
|
2656
|
-
if (opts.json) {
|
|
2657
|
-
console.log(JSON.stringify(status, null, 2));
|
|
2658
|
-
}
|
|
2659
|
-
else {
|
|
2660
|
-
console.log(formatStatus(status));
|
|
2661
|
-
if (status.conflicts.length > 0) {
|
|
2662
|
-
console.log("\nConflicts:");
|
|
2663
|
-
for (const c of status.conflicts) {
|
|
2664
|
-
console.log(` ${c.memoryId}: ${c.title}`);
|
|
2665
|
-
console.log(` local: ${c.localModified}`);
|
|
2666
|
-
console.log(` remote: ${c.remoteModified}`);
|
|
2667
|
-
}
|
|
2668
|
-
console.log("\nResolve with: gnosys remote resolve <memory-id> --keep <local|remote>");
|
|
2669
|
-
}
|
|
2670
|
-
}
|
|
2671
|
-
}
|
|
2672
|
-
catch (err) {
|
|
2673
|
-
console.error(`Error: ${err instanceof Error ? err.message : err}`);
|
|
2674
|
-
process.exit(1);
|
|
2675
|
-
}
|
|
2676
|
-
finally {
|
|
2677
|
-
centralDb?.close();
|
|
2678
|
-
}
|
|
2679
|
-
});
|
|
2680
|
-
remoteCmd
|
|
2681
|
-
.command("push")
|
|
2682
|
-
.description("Push local changes to remote")
|
|
2683
|
-
.option("--newer-wins", "Auto-resolve conflicts by taking the newer version")
|
|
2684
|
-
.action(async (opts) => {
|
|
2685
|
-
let centralDb = null;
|
|
2686
|
-
try {
|
|
2687
|
-
centralDb = GnosysDB.openLocal();
|
|
2688
|
-
if (!centralDb.isAvailable()) {
|
|
2689
|
-
console.error("Central DB not available.");
|
|
2690
|
-
process.exit(1);
|
|
2691
|
-
}
|
|
2692
|
-
const remotePath = centralDb.getMeta("remote_path");
|
|
2693
|
-
if (!remotePath) {
|
|
2694
|
-
console.error("Remote not configured.");
|
|
2695
|
-
process.exit(1);
|
|
2696
|
-
}
|
|
2697
|
-
const { RemoteSync } = await import("./lib/remote.js");
|
|
2698
|
-
const sync = new RemoteSync(centralDb, remotePath);
|
|
2699
|
-
const result = await sync.push({ strategy: opts.newerWins ? "newer-wins" : "skip-and-flag" });
|
|
2700
|
-
sync.closeRemote();
|
|
2701
|
-
const projParts = (result.projectsPushed || 0) > 0 ? ` | Projects pushed: ${result.projectsPushed}` : "";
|
|
2702
|
-
console.log(`Pushed: ${result.pushed} | Skipped: ${result.skipped} | Conflicts: ${result.conflicts.length}${projParts}`);
|
|
2703
|
-
if (result.errors.length > 0) {
|
|
2704
|
-
console.log("\nErrors:");
|
|
2705
|
-
for (const e of result.errors)
|
|
2706
|
-
console.log(` ${e}`);
|
|
2707
|
-
}
|
|
2708
|
-
if (result.conflicts.length > 0) {
|
|
2709
|
-
console.log("\nConflicts flagged (run 'gnosys remote status' for details):");
|
|
2710
|
-
for (const c of result.conflicts)
|
|
2711
|
-
console.log(` ${c.memoryId} — ${c.title}`);
|
|
2712
|
-
}
|
|
2713
|
-
}
|
|
2714
|
-
catch (err) {
|
|
2715
|
-
console.error(`Error: ${err instanceof Error ? err.message : err}`);
|
|
2716
|
-
process.exit(1);
|
|
2717
|
-
}
|
|
2718
|
-
finally {
|
|
2719
|
-
centralDb?.close();
|
|
2720
|
-
}
|
|
2721
|
-
});
|
|
2722
|
-
remoteCmd
|
|
2723
|
-
.command("pull")
|
|
2724
|
-
.description("Pull remote changes to local")
|
|
2725
|
-
.option("--newer-wins", "Auto-resolve conflicts by taking the newer version")
|
|
2726
|
-
.action(async (opts) => {
|
|
2727
|
-
let centralDb = null;
|
|
2728
|
-
try {
|
|
2729
|
-
centralDb = GnosysDB.openLocal();
|
|
2730
|
-
if (!centralDb.isAvailable()) {
|
|
2731
|
-
console.error("Central DB not available.");
|
|
2732
|
-
process.exit(1);
|
|
2733
|
-
}
|
|
2734
|
-
const remotePath = centralDb.getMeta("remote_path");
|
|
2735
|
-
if (!remotePath) {
|
|
2736
|
-
console.error("Remote not configured.");
|
|
2737
|
-
process.exit(1);
|
|
2738
|
-
}
|
|
2739
|
-
const { RemoteSync } = await import("./lib/remote.js");
|
|
2740
|
-
const sync = new RemoteSync(centralDb, remotePath);
|
|
2741
|
-
const result = await sync.pull({ strategy: opts.newerWins ? "newer-wins" : "skip-and-flag" });
|
|
2742
|
-
sync.closeRemote();
|
|
2743
|
-
const projParts = (result.projectsPulled || 0) > 0 ? ` | Projects pulled: ${result.projectsPulled}` : "";
|
|
2744
|
-
console.log(`Pulled: ${result.pulled} | Skipped: ${result.skipped} | Conflicts: ${result.conflicts.length}${projParts}`);
|
|
2745
|
-
if (result.errors.length > 0) {
|
|
2746
|
-
console.log("\nErrors:");
|
|
2747
|
-
for (const e of result.errors)
|
|
2748
|
-
console.log(` ${e}`);
|
|
2749
|
-
}
|
|
2750
|
-
}
|
|
2751
|
-
catch (err) {
|
|
2752
|
-
console.error(`Error: ${err instanceof Error ? err.message : err}`);
|
|
2753
|
-
process.exit(1);
|
|
2754
|
-
}
|
|
2755
|
-
finally {
|
|
2756
|
-
centralDb?.close();
|
|
2757
|
-
}
|
|
2758
|
-
});
|
|
2759
|
-
remoteCmd
|
|
2760
|
-
.command("sync")
|
|
2761
|
-
.description("Two-way sync: push local changes then pull remote changes")
|
|
2762
|
-
.option("--auto", "Run silently for cron/LaunchAgent (skip-and-flag for conflicts)")
|
|
2763
|
-
.option("--newer-wins", "Auto-resolve conflicts by taking the newer version")
|
|
2764
|
-
.action(async (opts) => {
|
|
2765
|
-
let centralDb = null;
|
|
2766
|
-
try {
|
|
2767
|
-
centralDb = GnosysDB.openLocal();
|
|
2768
|
-
if (!centralDb.isAvailable()) {
|
|
2769
|
-
if (!opts.auto)
|
|
2770
|
-
console.error("Central DB not available.");
|
|
2771
|
-
process.exit(1);
|
|
2772
|
-
}
|
|
2773
|
-
const remotePath = centralDb.getMeta("remote_path");
|
|
2774
|
-
if (!remotePath) {
|
|
2775
|
-
if (!opts.auto)
|
|
2776
|
-
console.error("Remote not configured.");
|
|
2777
|
-
process.exit(opts.auto ? 0 : 1);
|
|
2778
|
-
}
|
|
2779
|
-
const { RemoteSync } = await import("./lib/remote.js");
|
|
2780
|
-
const sync = new RemoteSync(centralDb, remotePath);
|
|
2781
|
-
const result = await sync.sync({
|
|
2782
|
-
auto: opts.auto,
|
|
2783
|
-
strategy: opts.newerWins ? "newer-wins" : "skip-and-flag",
|
|
2784
|
-
});
|
|
2785
|
-
sync.closeRemote();
|
|
2786
|
-
if (!opts.auto || result.conflicts.length > 0 || result.errors.length > 0) {
|
|
2787
|
-
const pp = result.projectsPushed || 0;
|
|
2788
|
-
const pl = result.projectsPulled || 0;
|
|
2789
|
-
const projParts = (pp + pl) > 0 ? ` | Projects: ↑${pp}/↓${pl}` : "";
|
|
2790
|
-
console.log(`Pushed: ${result.pushed} | Pulled: ${result.pulled} | Conflicts: ${result.conflicts.length}${projParts}`);
|
|
2791
|
-
if (result.errors.length > 0) {
|
|
2792
|
-
console.log("\nErrors:");
|
|
2793
|
-
for (const e of result.errors)
|
|
2794
|
-
console.log(` ${e}`);
|
|
2795
|
-
}
|
|
2796
|
-
if (result.conflicts.length > 0) {
|
|
2797
|
-
console.log("\nConflicts need resolution (run 'gnosys remote status' for details).");
|
|
2798
|
-
}
|
|
3031
|
+
dashDb = db;
|
|
2799
3032
|
}
|
|
2800
3033
|
}
|
|
2801
|
-
catch
|
|
2802
|
-
|
|
2803
|
-
console.error(`Error: ${err instanceof Error ? err.message : err}`);
|
|
2804
|
-
process.exit(1);
|
|
3034
|
+
catch {
|
|
3035
|
+
// Central DB not available — legacy dashboard only
|
|
2805
3036
|
}
|
|
2806
|
-
|
|
2807
|
-
|
|
3037
|
+
const data = await collectDashboardData(resolver, cfg, pkg.version, dashDb);
|
|
3038
|
+
if (opts.json) {
|
|
3039
|
+
console.log(formatDashboardJSON(data));
|
|
3040
|
+
}
|
|
3041
|
+
else {
|
|
3042
|
+
console.log(formatDashboard(data));
|
|
2808
3043
|
}
|
|
2809
3044
|
});
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
.
|
|
2813
|
-
.
|
|
2814
|
-
.
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
if (!remotePath) {
|
|
2824
|
-
console.error("Remote not configured.");
|
|
2825
|
-
process.exit(1);
|
|
2826
|
-
}
|
|
2827
|
-
if (opts.keep !== "local" && opts.keep !== "remote") {
|
|
2828
|
-
console.error(`--keep must be 'local' or 'remote' (got: ${opts.keep})`);
|
|
2829
|
-
process.exit(1);
|
|
2830
|
-
}
|
|
2831
|
-
const { RemoteSync } = await import("./lib/remote.js");
|
|
2832
|
-
const sync = new RemoteSync(centralDb, remotePath);
|
|
2833
|
-
const result = await sync.resolve(memoryId, opts.keep);
|
|
2834
|
-
sync.closeRemote();
|
|
2835
|
-
if (result.ok) {
|
|
2836
|
-
console.log(`Resolved ${memoryId}: kept ${opts.keep} version.`);
|
|
2837
|
-
}
|
|
2838
|
-
else {
|
|
2839
|
-
console.error(`Failed to resolve: ${result.error}`);
|
|
2840
|
-
process.exit(1);
|
|
2841
|
-
}
|
|
3045
|
+
// ─── gnosys maintain ─────────────────────────────────────────────────────
|
|
3046
|
+
program
|
|
3047
|
+
.command("maintain")
|
|
3048
|
+
.description("Run vault maintenance: detect duplicates, apply confidence decay, consolidate similar memories")
|
|
3049
|
+
.option("--dry-run", "Show what would change without modifying anything")
|
|
3050
|
+
.option("--auto-apply", "Automatically apply all changes (no prompts)")
|
|
3051
|
+
.action(async (opts) => {
|
|
3052
|
+
const { GnosysMaintenanceEngine, formatMaintenanceReport } = await import("./lib/maintenance.js");
|
|
3053
|
+
const resolver = await getResolver();
|
|
3054
|
+
const stores = resolver.getStores();
|
|
3055
|
+
if (stores.length === 0) {
|
|
3056
|
+
console.error("No Gnosys stores found. Run gnosys init first.");
|
|
3057
|
+
process.exit(1);
|
|
2842
3058
|
}
|
|
2843
|
-
|
|
2844
|
-
|
|
3059
|
+
const cfg = await loadConfig(stores[0].path);
|
|
3060
|
+
const engine = new GnosysMaintenanceEngine(resolver, cfg);
|
|
3061
|
+
const report = await engine.maintain({
|
|
3062
|
+
dryRun: opts.dryRun,
|
|
3063
|
+
autoApply: opts.autoApply,
|
|
3064
|
+
onLog: (level, message) => {
|
|
3065
|
+
if (level === "warn") {
|
|
3066
|
+
console.error(`⚠ ${message}`);
|
|
3067
|
+
}
|
|
3068
|
+
else if (level === "action") {
|
|
3069
|
+
console.log(`→ ${message}`);
|
|
3070
|
+
}
|
|
3071
|
+
else {
|
|
3072
|
+
console.log(message);
|
|
3073
|
+
}
|
|
3074
|
+
},
|
|
3075
|
+
onProgress: (step, current, total) => {
|
|
3076
|
+
process.stdout.write(`\r[${current}/${total}] ${step}...`);
|
|
3077
|
+
if (current === total)
|
|
3078
|
+
process.stdout.write("\n");
|
|
3079
|
+
},
|
|
3080
|
+
});
|
|
3081
|
+
console.log("");
|
|
3082
|
+
console.log(formatMaintenanceReport(report));
|
|
3083
|
+
});
|
|
3084
|
+
// ─── gnosys dearchive ───────────────────────────────────────────────────
|
|
3085
|
+
program
|
|
3086
|
+
.command("dearchive <query>")
|
|
3087
|
+
.description("Force-dearchive memories matching a query from archive.db back to active")
|
|
3088
|
+
.option("--limit <n>", "Max memories to dearchive", "5")
|
|
3089
|
+
.action(async (query, opts) => {
|
|
3090
|
+
const { GnosysArchive } = await import("./lib/archive.js");
|
|
3091
|
+
const resolver = await getResolver();
|
|
3092
|
+
const stores = resolver.getStores();
|
|
3093
|
+
if (stores.length === 0) {
|
|
3094
|
+
console.error("No Gnosys stores found. Run gnosys init first.");
|
|
2845
3095
|
process.exit(1);
|
|
2846
3096
|
}
|
|
2847
|
-
|
|
2848
|
-
|
|
3097
|
+
const writeTarget = resolver.getWriteTarget();
|
|
3098
|
+
if (!writeTarget) {
|
|
3099
|
+
console.error("No writable store found.");
|
|
3100
|
+
process.exit(1);
|
|
3101
|
+
}
|
|
3102
|
+
const archive = new GnosysArchive(writeTarget.path);
|
|
3103
|
+
if (!archive.isAvailable()) {
|
|
3104
|
+
console.error("Archive not available. Is better-sqlite3 installed?");
|
|
3105
|
+
process.exit(1);
|
|
3106
|
+
}
|
|
3107
|
+
const results = archive.searchArchive(query, parseInt(opts.limit));
|
|
3108
|
+
if (results.length === 0) {
|
|
3109
|
+
console.log(`No archived memories found matching "${query}".`);
|
|
3110
|
+
archive.close();
|
|
3111
|
+
return;
|
|
3112
|
+
}
|
|
3113
|
+
console.log(`Found ${results.length} archived memories matching "${query}":\n`);
|
|
3114
|
+
for (const r of results) {
|
|
3115
|
+
console.log(` • ${r.title} (${r.id})`);
|
|
3116
|
+
}
|
|
3117
|
+
console.log("");
|
|
3118
|
+
// Dearchive all found
|
|
3119
|
+
const ids = results.map((r) => r.id);
|
|
3120
|
+
const restored = await archive.dearchiveBatch(ids, writeTarget.store);
|
|
3121
|
+
archive.close();
|
|
3122
|
+
console.log(`Dearchived ${restored.length} memories back to active:`);
|
|
3123
|
+
for (const rp of restored) {
|
|
3124
|
+
console.log(` → ${rp}`);
|
|
2849
3125
|
}
|
|
2850
3126
|
});
|
|
2851
|
-
//
|
|
2852
|
-
// canonical `gnosys setup remote` form (which calls the same wizard
|
|
2853
|
-
// helpers from lib/remoteWizard.ts). Sync operations like push/pull/sync
|
|
2854
|
-
// remain under the `remote` parent.
|
|
3127
|
+
// NOTE: gnosys migrate is defined below (near the end) with --to-central support
|
|
2855
3128
|
// ─── gnosys upgrade ─────────────────────────────────────────────────────
|
|
2856
3129
|
program
|
|
2857
3130
|
.command("upgrade")
|
|
@@ -3081,7 +3354,8 @@ program
|
|
|
3081
3354
|
program
|
|
3082
3355
|
.command("doctor")
|
|
3083
3356
|
.description("Check system health: stores, LLM connectivity, embeddings, archive")
|
|
3084
|
-
.
|
|
3357
|
+
.option("--fix", "Offer interactive cleanup of legacy artifacts (e.g. per-store gnosys.db)")
|
|
3358
|
+
.action(async (opts) => {
|
|
3085
3359
|
const resolver = await getResolver();
|
|
3086
3360
|
const stores = resolver.getStores();
|
|
3087
3361
|
console.log("Gnosys Doctor");
|
|
@@ -3092,9 +3366,36 @@ program
|
|
|
3092
3366
|
const localDbExists = await fs.stat(localDbPath).then(() => true).catch(() => false);
|
|
3093
3367
|
if (localDbExists) {
|
|
3094
3368
|
console.log("Local Store (gnosys.db):");
|
|
3095
|
-
console.log(" ⚠ Local gnosys.db found — this is a legacy artifact.");
|
|
3096
|
-
console.log(" All memories
|
|
3097
|
-
console.log(`
|
|
3369
|
+
console.log(" ⚠ Local gnosys.db found — this is a legacy artifact (pre-v2.0 file-based store).");
|
|
3370
|
+
console.log(" All memories live in the central DB now (~/.gnosys/gnosys.db).");
|
|
3371
|
+
console.log(` Path: ${localDbPath}`);
|
|
3372
|
+
if (opts.fix) {
|
|
3373
|
+
// Interactive cleanup — verify the local DB is safe to delete
|
|
3374
|
+
// (no rows that aren't already in the central DB) before prompting.
|
|
3375
|
+
const safe = await isLegacyStoreSafeToRemove(localDbPath);
|
|
3376
|
+
if (!safe.ok) {
|
|
3377
|
+
console.log(` ✗ NOT safe to auto-remove: ${safe.reason}`);
|
|
3378
|
+
console.log(` Inspect manually with: sqlite3 ${localDbPath} "SELECT COUNT(*) FROM memories;"`);
|
|
3379
|
+
}
|
|
3380
|
+
else {
|
|
3381
|
+
const readline = await import("readline/promises");
|
|
3382
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
3383
|
+
const answer = await rl.question(` Remove "${localDbPath}"? [y/N] `);
|
|
3384
|
+
rl.close();
|
|
3385
|
+
if (answer.trim().toLowerCase() === "y") {
|
|
3386
|
+
await fs.unlink(localDbPath).catch(() => undefined);
|
|
3387
|
+
await fs.unlink(localDbPath + "-wal").catch(() => undefined);
|
|
3388
|
+
await fs.unlink(localDbPath + "-shm").catch(() => undefined);
|
|
3389
|
+
console.log(" ✓ Removed.");
|
|
3390
|
+
}
|
|
3391
|
+
else {
|
|
3392
|
+
console.log(" Skipped.");
|
|
3393
|
+
}
|
|
3394
|
+
}
|
|
3395
|
+
}
|
|
3396
|
+
else {
|
|
3397
|
+
console.log(" Run 'gnosys doctor --fix' to remove safely (after verifying it's empty).");
|
|
3398
|
+
}
|
|
3098
3399
|
console.log("");
|
|
3099
3400
|
}
|
|
3100
3401
|
}
|
|
@@ -3206,31 +3507,110 @@ program
|
|
|
3206
3507
|
catch {
|
|
3207
3508
|
console.log(" Index: not initialized (run gnosys reindex to build)");
|
|
3208
3509
|
}
|
|
3209
|
-
// Maintenance health
|
|
3510
|
+
// Maintenance health — v5.7.0: queries the central DB directly
|
|
3511
|
+
// (the prior version used GnosysMaintenanceEngine which only sees the
|
|
3512
|
+
// legacy file-based stores, which are empty post-DB-only).
|
|
3210
3513
|
console.log("");
|
|
3211
3514
|
console.log("Maintenance Health:");
|
|
3212
3515
|
try {
|
|
3213
|
-
const
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3516
|
+
const db2 = GnosysDB.openCentral();
|
|
3517
|
+
if (db2.isAvailable() && db2.isMigrated()) {
|
|
3518
|
+
const memories = db2.getActiveMemories();
|
|
3519
|
+
const now = Date.now();
|
|
3520
|
+
const DECAY_LAMBDA = 0.005;
|
|
3521
|
+
const STALE_THRESHOLD = 0.3;
|
|
3522
|
+
let sumConfidence = 0;
|
|
3523
|
+
let sumDecayed = 0;
|
|
3524
|
+
let staleCount = 0;
|
|
3525
|
+
let neverReinforced = 0;
|
|
3526
|
+
let totalReinforcements = 0;
|
|
3527
|
+
for (const m of memories) {
|
|
3528
|
+
const baseConfidence = m.confidence ?? 0.8;
|
|
3529
|
+
const lastIso = m.last_reinforced || m.modified || m.created;
|
|
3530
|
+
const lastTs = lastIso ? new Date(lastIso).getTime() : NaN;
|
|
3531
|
+
// Some legacy memories have non-ISO dates that don't parse; treat
|
|
3532
|
+
// them as "today" rather than NaN-corrupting the average.
|
|
3533
|
+
const daysSince = Number.isFinite(lastTs)
|
|
3534
|
+
? Math.max(0, Math.floor((now - lastTs) / (1000 * 60 * 60 * 24)))
|
|
3535
|
+
: 0;
|
|
3536
|
+
const decayed = baseConfidence * Math.exp(-DECAY_LAMBDA * daysSince);
|
|
3537
|
+
sumConfidence += baseConfidence;
|
|
3538
|
+
sumDecayed += decayed;
|
|
3539
|
+
if (decayed < STALE_THRESHOLD)
|
|
3540
|
+
staleCount++;
|
|
3541
|
+
const rc = m.reinforcement_count ?? 0;
|
|
3542
|
+
if (rc === 0)
|
|
3543
|
+
neverReinforced++;
|
|
3544
|
+
totalReinforcements += rc;
|
|
3545
|
+
}
|
|
3546
|
+
const n = Math.max(1, memories.length);
|
|
3547
|
+
console.log(` Active memories: ${memories.length}`);
|
|
3548
|
+
console.log(` Stale (decayed confidence < ${STALE_THRESHOLD}): ${staleCount}`);
|
|
3549
|
+
console.log(` Average confidence: ${(sumConfidence / n).toFixed(3)} (decayed: ${(sumDecayed / n).toFixed(3)})`);
|
|
3550
|
+
console.log(` Never reinforced: ${neverReinforced}`);
|
|
3551
|
+
console.log(` Total reinforcements: ${totalReinforcements}`);
|
|
3552
|
+
}
|
|
3553
|
+
else {
|
|
3554
|
+
console.log(" — central DB not available");
|
|
3555
|
+
}
|
|
3556
|
+
db2.close();
|
|
3221
3557
|
}
|
|
3222
3558
|
catch (err) {
|
|
3223
3559
|
console.log(` Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
3224
3560
|
}
|
|
3225
3561
|
}
|
|
3226
3562
|
});
|
|
3563
|
+
/**
|
|
3564
|
+
* Check whether a legacy per-store gnosys.db is safe to remove.
|
|
3565
|
+
* Safe = the file is empty OR every memory in it is already represented
|
|
3566
|
+
* in the central DB (matching ID present centrally). This is conservative:
|
|
3567
|
+
* we don't compare hashes or content, just IDs. The legacy DB existed
|
|
3568
|
+
* pre-v2.0; its memories should have all migrated to central DB long ago.
|
|
3569
|
+
*/
|
|
3570
|
+
async function isLegacyStoreSafeToRemove(localDbPath) {
|
|
3571
|
+
try {
|
|
3572
|
+
const Database = (await import("better-sqlite3")).default;
|
|
3573
|
+
const localDb = new Database(localDbPath, { readonly: true });
|
|
3574
|
+
let localIds = [];
|
|
3575
|
+
try {
|
|
3576
|
+
const rows = localDb.prepare("SELECT id FROM memories").all();
|
|
3577
|
+
localIds = rows.map((r) => r.id);
|
|
3578
|
+
}
|
|
3579
|
+
catch {
|
|
3580
|
+
// Table doesn't exist — file is effectively empty
|
|
3581
|
+
localDb.close();
|
|
3582
|
+
return { ok: true };
|
|
3583
|
+
}
|
|
3584
|
+
localDb.close();
|
|
3585
|
+
if (localIds.length === 0)
|
|
3586
|
+
return { ok: true };
|
|
3587
|
+
const centralDb = GnosysDB.openCentral();
|
|
3588
|
+
if (!centralDb.isAvailable()) {
|
|
3589
|
+
centralDb.close();
|
|
3590
|
+
return { ok: false, reason: "central DB unavailable — cannot verify migration" };
|
|
3591
|
+
}
|
|
3592
|
+
let missing = 0;
|
|
3593
|
+
for (const id of localIds) {
|
|
3594
|
+
if (!centralDb.getMemory(id))
|
|
3595
|
+
missing++;
|
|
3596
|
+
}
|
|
3597
|
+
centralDb.close();
|
|
3598
|
+
if (missing > 0) {
|
|
3599
|
+
return { ok: false, reason: `${missing} of ${localIds.length} local memories not found in central DB` };
|
|
3600
|
+
}
|
|
3601
|
+
return { ok: true };
|
|
3602
|
+
}
|
|
3603
|
+
catch (err) {
|
|
3604
|
+
return { ok: false, reason: `inspection failed: ${err instanceof Error ? err.message : String(err)}` };
|
|
3605
|
+
}
|
|
3606
|
+
}
|
|
3227
3607
|
// ─── gnosys check ─────────────────────────────────────────────────────────
|
|
3228
3608
|
program
|
|
3229
3609
|
.command("check")
|
|
3230
|
-
.description("Test LLM connectivity for
|
|
3231
|
-
.option("-
|
|
3610
|
+
.description("Test LLM connectivity for each configured task (structuring, synthesis, chat, vision, transcription, dream)")
|
|
3611
|
+
.option("-t, --task <name>", "Test only one task (structuring | synthesis | chat | vision | transcription | dream)")
|
|
3232
3612
|
.action(async (opts) => {
|
|
3233
|
-
const projectDir =
|
|
3613
|
+
const projectDir = process.cwd();
|
|
3234
3614
|
const storePath = path.join(projectDir, ".gnosys");
|
|
3235
3615
|
const globalStorePath = getGnosysHome();
|
|
3236
3616
|
// Load config: try project-level first, fall back to global ~/.gnosys/
|
|
@@ -3275,6 +3655,13 @@ program
|
|
|
3275
3655
|
description: "Q&A answers (gnosys ask)",
|
|
3276
3656
|
resolve: () => resolveTaskModel(cfg, "synthesis"),
|
|
3277
3657
|
},
|
|
3658
|
+
{
|
|
3659
|
+
name: "chat",
|
|
3660
|
+
description: "interactive chat (gnosys chat)",
|
|
3661
|
+
// Chat reuses the synthesis task's model — surface it under its own name
|
|
3662
|
+
// so users can see exactly what their TUI will use.
|
|
3663
|
+
resolve: () => resolveTaskModel(cfg, "synthesis"),
|
|
3664
|
+
},
|
|
3278
3665
|
{
|
|
3279
3666
|
name: "vision",
|
|
3280
3667
|
description: "images, PDFs",
|
|
@@ -3297,7 +3684,15 @@ program
|
|
|
3297
3684
|
let passed = 0;
|
|
3298
3685
|
let failed = 0;
|
|
3299
3686
|
let skipped = 0;
|
|
3300
|
-
|
|
3687
|
+
// Filter to a single task if --task was given.
|
|
3688
|
+
const filteredTasks = opts.task
|
|
3689
|
+
? tasks.filter((t) => t.name === opts.task)
|
|
3690
|
+
: tasks;
|
|
3691
|
+
if (opts.task && filteredTasks.length === 0) {
|
|
3692
|
+
console.error(`Unknown task: ${opts.task}. Pick one of: ${tasks.map((t) => t.name).join(", ")}`);
|
|
3693
|
+
process.exit(1);
|
|
3694
|
+
}
|
|
3695
|
+
for (const task of filteredTasks) {
|
|
3301
3696
|
const { provider, model } = task.resolve();
|
|
3302
3697
|
const label = `${task.name.padEnd(16)} ${DIM}${provider} / ${model}${RESET}`;
|
|
3303
3698
|
const desc = `${DIM}(${task.description})${RESET}`;
|
|
@@ -3495,18 +3890,7 @@ dreamCmd
|
|
|
3495
3890
|
centralDb?.close();
|
|
3496
3891
|
}
|
|
3497
3892
|
});
|
|
3498
|
-
|
|
3499
|
-
program
|
|
3500
|
-
.command("export")
|
|
3501
|
-
.description("Export gnosys.db to Obsidian-compatible vault (one-way)")
|
|
3502
|
-
.requiredOption("--to <dir>", "Target directory for export")
|
|
3503
|
-
.option("--all", "Export all memories (active + archived)")
|
|
3504
|
-
.option("--overwrite", "Overwrite existing files")
|
|
3505
|
-
.option("--no-summaries", "Skip category summaries")
|
|
3506
|
-
.option("--no-reviews", "Skip review suggestions")
|
|
3507
|
-
.option("--no-graph", "Skip relationship graph")
|
|
3508
|
-
.option("--json", "Output raw JSON report")
|
|
3509
|
-
.action(async (opts) => {
|
|
3893
|
+
async function runVaultExport(opts) {
|
|
3510
3894
|
const resolver = new GnosysResolver();
|
|
3511
3895
|
await resolver.resolve();
|
|
3512
3896
|
const stores = resolver.getStores();
|
|
@@ -3545,11 +3929,86 @@ program
|
|
|
3545
3929
|
console.log(formatExportReport(report));
|
|
3546
3930
|
}
|
|
3547
3931
|
db.close();
|
|
3932
|
+
}
|
|
3933
|
+
const exportCmd = program
|
|
3934
|
+
.command("export")
|
|
3935
|
+
.description("Export memory to a vault (markdown) or a project bundle (.json.gz)")
|
|
3936
|
+
.enablePositionalOptions();
|
|
3937
|
+
// Bare `gnosys export` shows the canonical subcommand forms. Back-compat for
|
|
3938
|
+
// the v5.5.x form `gnosys export --to <dir>` is handled in a pre-parse shim
|
|
3939
|
+
// at the top of the file (rewrites argv to insert "vault" before "--to").
|
|
3940
|
+
exportCmd.action(() => {
|
|
3941
|
+
console.error("Usage: gnosys export vault --to <dir> # Obsidian vault export");
|
|
3942
|
+
console.error(" gnosys export project [id] --to <bundle> # portable .json.gz bundle");
|
|
3943
|
+
process.exit(1);
|
|
3944
|
+
});
|
|
3945
|
+
// `gnosys export vault` — explicit alias for the default behavior
|
|
3946
|
+
exportCmd
|
|
3947
|
+
.command("vault")
|
|
3948
|
+
.description("Export gnosys.db to an Obsidian-compatible vault (one-way)")
|
|
3949
|
+
.requiredOption("--to <dir>", "Target directory for export")
|
|
3950
|
+
.option("--all", "Export all memories (active + archived)")
|
|
3951
|
+
.option("--overwrite", "Overwrite existing files")
|
|
3952
|
+
.option("--no-summaries", "Skip category summaries")
|
|
3953
|
+
.option("--no-reviews", "Skip review suggestions")
|
|
3954
|
+
.option("--no-graph", "Skip relationship graph")
|
|
3955
|
+
.option("--json", "Output raw JSON report")
|
|
3956
|
+
.action(runVaultExport);
|
|
3957
|
+
// `gnosys export project [id]` — bundle a single project for portability
|
|
3958
|
+
exportCmd
|
|
3959
|
+
.command("project [projectId]")
|
|
3960
|
+
.description("Export a single project to a portable .json.gz bundle (round-trips with 'gnosys import project')")
|
|
3961
|
+
.requiredOption("--to <file>", "Output bundle file path (e.g. ./gnosys-public.gnosys.json.gz)")
|
|
3962
|
+
.option("--include-archived", "Include archived and superseded memories (default: active only)")
|
|
3963
|
+
.option("--no-audit", "Skip the audit log")
|
|
3964
|
+
.option("--json", "Output the result as JSON")
|
|
3965
|
+
.action(async (projectIdArg, opts) => {
|
|
3966
|
+
const { GnosysDB: DbClass } = await import("./lib/db.js");
|
|
3967
|
+
const { exportProject } = await import("./lib/exportProject.js");
|
|
3968
|
+
const centralDb = DbClass.openCentral();
|
|
3969
|
+
if (!centralDb.isAvailable()) {
|
|
3970
|
+
console.error("Central DB unavailable.");
|
|
3971
|
+
process.exit(1);
|
|
3972
|
+
}
|
|
3973
|
+
let projectId = projectIdArg;
|
|
3974
|
+
if (!projectId) {
|
|
3975
|
+
// Auto-detect from cwd
|
|
3976
|
+
const proj = centralDb.getProjectByDirectory(process.cwd());
|
|
3977
|
+
if (!proj) {
|
|
3978
|
+
console.error("No project ID given and current directory is not a registered project.");
|
|
3979
|
+
console.error("Usage: gnosys export project <projectId> --to <file>");
|
|
3980
|
+
process.exit(1);
|
|
3981
|
+
}
|
|
3982
|
+
projectId = proj.id;
|
|
3983
|
+
}
|
|
3984
|
+
try {
|
|
3985
|
+
const result = exportProject(centralDb, {
|
|
3986
|
+
projectId,
|
|
3987
|
+
outputPath: path.resolve(opts.to),
|
|
3988
|
+
includeArchived: !!opts.includeArchived,
|
|
3989
|
+
includeAudit: opts.audit !== false,
|
|
3990
|
+
});
|
|
3991
|
+
if (opts.json) {
|
|
3992
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3993
|
+
}
|
|
3994
|
+
else {
|
|
3995
|
+
const ratio = (result.compressedBytes / result.uncompressedBytes * 100).toFixed(1);
|
|
3996
|
+
console.log(`Exported project ${projectId}`);
|
|
3997
|
+
console.log(` Memories: ${result.memoryCount}`);
|
|
3998
|
+
console.log(` Relationships: ${result.relationshipCount}`);
|
|
3999
|
+
console.log(` Audit entries: ${result.auditEntryCount}`);
|
|
4000
|
+
console.log(` Bundle: ${result.outputPath}`);
|
|
4001
|
+
console.log(` Size: ${(result.compressedBytes / 1024).toFixed(1)} KB compressed (${ratio}% of ${(result.uncompressedBytes / 1024).toFixed(1)} KB)`);
|
|
4002
|
+
}
|
|
4003
|
+
}
|
|
4004
|
+
finally {
|
|
4005
|
+
centralDb.close();
|
|
4006
|
+
}
|
|
3548
4007
|
});
|
|
3549
4008
|
// ─── gnosys serve ────────────────────────────────────────────────────────
|
|
3550
4009
|
program
|
|
3551
4010
|
.command("serve")
|
|
3552
|
-
.description("Start the MCP server (stdio mode)")
|
|
4011
|
+
.description("Start the MCP server (stdio mode). Used by IDE integrations — Claude Code/Desktop, Cursor, Codex, etc. spawn this command in the background to talk to gnosys via the Model Context Protocol. You don't normally invoke this yourself; `gnosys init <ide>` wires it into the IDE config.")
|
|
3553
4012
|
.option("--with-maintenance", "Run maintenance every 6 hours in background")
|
|
3554
4013
|
.action(async (opts) => {
|
|
3555
4014
|
if (opts.withMaintenance) {
|
|
@@ -3704,31 +4163,33 @@ program
|
|
|
3704
4163
|
// ─── gnosys audit ────────────────────────────────────────────────────────
|
|
3705
4164
|
program
|
|
3706
4165
|
.command("audit")
|
|
3707
|
-
.description("View the structured audit trail of memory operations")
|
|
4166
|
+
.description("View the structured audit trail of memory operations from the central DB")
|
|
3708
4167
|
.option("--days <n>", "Show entries from the last N days", "7")
|
|
3709
|
-
.option("--operation <op>", "Filter by operation type (read, write, recall, etc.)")
|
|
4168
|
+
.option("--operation <op>", "Filter by operation type (read, write, recall, dream_*, etc.)")
|
|
3710
4169
|
.option("--limit <n>", "Max entries to show")
|
|
3711
4170
|
.option("--json", "Output raw JSON instead of formatted timeline")
|
|
3712
4171
|
.action(async (opts) => {
|
|
3713
|
-
const
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
console.error("No Gnosys stores found. Run 'gnosys init' first.");
|
|
4172
|
+
const { readAuditFromDb, formatAuditTimeline } = await import("./lib/audit.js");
|
|
4173
|
+
const centralDb = GnosysDB.openCentral();
|
|
4174
|
+
if (!centralDb.isAvailable()) {
|
|
4175
|
+
console.error("Central DB unavailable.");
|
|
3718
4176
|
process.exit(1);
|
|
3719
4177
|
}
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
4178
|
+
try {
|
|
4179
|
+
const entries = readAuditFromDb(centralDb, {
|
|
4180
|
+
days: parseInt(opts.days, 10),
|
|
4181
|
+
operation: opts.operation,
|
|
4182
|
+
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
|
|
4183
|
+
});
|
|
4184
|
+
if (opts.json) {
|
|
4185
|
+
console.log(JSON.stringify(entries, null, 2));
|
|
4186
|
+
}
|
|
4187
|
+
else {
|
|
4188
|
+
console.log(formatAuditTimeline(entries));
|
|
4189
|
+
}
|
|
3729
4190
|
}
|
|
3730
|
-
|
|
3731
|
-
|
|
4191
|
+
finally {
|
|
4192
|
+
centralDb.close();
|
|
3732
4193
|
}
|
|
3733
4194
|
});
|
|
3734
4195
|
// ─── gnosys backup ──────────────────────────────────────────────────────
|
|
@@ -3930,8 +4391,10 @@ program
|
|
|
3930
4391
|
.command("projects")
|
|
3931
4392
|
.description("List registered projects from the central DB")
|
|
3932
4393
|
.option("--json", "Output as JSON")
|
|
3933
|
-
.option("--all", "Include dead projects (deleted directories
|
|
3934
|
-
.option("--prune", "Delete registry entries whose directory no longer exists
|
|
4394
|
+
.option("--all", "Include dead projects (deleted directories)")
|
|
4395
|
+
.option("--prune", "Delete registry entries whose directory no longer exists (interactive by default)")
|
|
4396
|
+
.option("--dry-run", "With --prune: list what would be deleted, don't actually delete")
|
|
4397
|
+
.option("--yes", "With --prune: skip the confirmation prompt (scripting/automation)")
|
|
3935
4398
|
.action(async (opts) => {
|
|
3936
4399
|
let centralDb = null;
|
|
3937
4400
|
try {
|
|
@@ -3942,8 +4405,37 @@ program
|
|
|
3942
4405
|
}
|
|
3943
4406
|
const allProjects = centralDb.getAllProjects();
|
|
3944
4407
|
if (opts.prune) {
|
|
3945
|
-
// Find
|
|
4408
|
+
// Find dead projects first — never just delete without showing
|
|
4409
|
+
// them. v5.7.0 adds confirmation by default; --yes skips for
|
|
4410
|
+
// scripted use; --dry-run shows the list without deleting.
|
|
3946
4411
|
const deadProjects = allProjects.filter((p) => isDeadProjectDir(p.working_directory));
|
|
4412
|
+
if (deadProjects.length === 0) {
|
|
4413
|
+
console.log("No dead projects to prune.");
|
|
4414
|
+
return;
|
|
4415
|
+
}
|
|
4416
|
+
const DIM = "\x1b[2m";
|
|
4417
|
+
const RESET = "\x1b[0m";
|
|
4418
|
+
// Always show what would be removed first.
|
|
4419
|
+
console.log(`Found ${deadProjects.length} dead project(s):\n`);
|
|
4420
|
+
for (const p of deadProjects) {
|
|
4421
|
+
const memCount = centralDb.getMemoriesByProject(p.id, true).length;
|
|
4422
|
+
console.log(` ${p.name} ${DIM}${p.working_directory}${RESET} (${memCount} memorie(s))`);
|
|
4423
|
+
}
|
|
4424
|
+
console.log();
|
|
4425
|
+
if (opts.dryRun) {
|
|
4426
|
+
console.log("[dry-run] No changes made. Re-run without --dry-run to delete.");
|
|
4427
|
+
return;
|
|
4428
|
+
}
|
|
4429
|
+
if (!opts.yes) {
|
|
4430
|
+
const readline = await import("readline/promises");
|
|
4431
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
4432
|
+
const answer = (await rl.question(`Delete these ${deadProjects.length} project registry entries? [y/N] `)).trim().toLowerCase();
|
|
4433
|
+
rl.close();
|
|
4434
|
+
if (answer !== "y" && answer !== "yes") {
|
|
4435
|
+
console.log("Cancelled.");
|
|
4436
|
+
return;
|
|
4437
|
+
}
|
|
4438
|
+
}
|
|
3947
4439
|
for (const p of deadProjects) {
|
|
3948
4440
|
centralDb.deleteProject(p.id);
|
|
3949
4441
|
}
|
|
@@ -3952,19 +4444,7 @@ program
|
|
|
3952
4444
|
remaining: allProjects.length - deadProjects.length,
|
|
3953
4445
|
deletedProjects: deadProjects.map((p) => ({ id: p.id, name: p.name, directory: p.working_directory })),
|
|
3954
4446
|
}, () => {
|
|
3955
|
-
|
|
3956
|
-
console.log("No dead projects to prune.");
|
|
3957
|
-
}
|
|
3958
|
-
else {
|
|
3959
|
-
const DIM = "\x1b[2m";
|
|
3960
|
-
const RESET = "\x1b[0m";
|
|
3961
|
-
console.log(`Pruned ${deadProjects.length} dead project(s):\n`);
|
|
3962
|
-
for (const p of deadProjects) {
|
|
3963
|
-
console.log(` ${p.name} ${DIM}${p.working_directory}${RESET}`);
|
|
3964
|
-
}
|
|
3965
|
-
console.log();
|
|
3966
|
-
console.log(`${allProjects.length - deadProjects.length} project(s) remain.`);
|
|
3967
|
-
}
|
|
4447
|
+
console.log(`✓ Pruned ${deadProjects.length} project(s). ${allProjects.length - deadProjects.length} remain.`);
|
|
3968
4448
|
});
|
|
3969
4449
|
return;
|
|
3970
4450
|
}
|
|
@@ -4026,7 +4506,7 @@ program
|
|
|
4026
4506
|
// ─── gnosys pref ─────────────────────────────────────────────────────────
|
|
4027
4507
|
const prefCmd = program
|
|
4028
4508
|
.command("pref")
|
|
4029
|
-
.description("
|
|
4509
|
+
.description("User preferences — small key-value memories scoped to you (not a project), surfaced into every agent's context. Use for cross-project conventions like 'prefer simple solutions' or 'no emoji in UI'. Subcommands: set, get, delete. Review/clean up with `gnosys setup preferences`.");
|
|
4030
4510
|
prefCmd
|
|
4031
4511
|
.command("set <key> <value>")
|
|
4032
4512
|
.description("Set a user preference. Key should be kebab-case (e.g. 'commit-convention').")
|
|
@@ -4285,13 +4765,13 @@ program
|
|
|
4285
4765
|
});
|
|
4286
4766
|
// ─── gnosys briefing ─────────────────────────────────────────────────────
|
|
4287
4767
|
program
|
|
4288
|
-
.command("briefing")
|
|
4768
|
+
.command("briefing [projectNameOrId]")
|
|
4289
4769
|
.description("Generate project briefing — memory state summary, categories, recent activity, top tags")
|
|
4290
4770
|
.option("-p, --project <id>", "Project ID (auto-detects if omitted)")
|
|
4291
4771
|
.option("-a, --all", "Generate briefings for all projects")
|
|
4292
4772
|
.option("-d, --directory <dir>", "Project directory for auto-detection")
|
|
4293
4773
|
.option("--json", "Output as JSON")
|
|
4294
|
-
.action(async (opts) => {
|
|
4774
|
+
.action(async (projectNameOrId, opts) => {
|
|
4295
4775
|
let centralDb = null;
|
|
4296
4776
|
try {
|
|
4297
4777
|
centralDb = GnosysDB.openCentral();
|
|
@@ -4317,7 +4797,26 @@ program
|
|
|
4317
4797
|
}
|
|
4318
4798
|
return;
|
|
4319
4799
|
}
|
|
4320
|
-
|
|
4800
|
+
// v5.7.0: accept project name as positional argument in addition to --project <id>.
|
|
4801
|
+
// Resolution order: positional name → --project flag → cwd auto-detect.
|
|
4802
|
+
let pid = opts.project ?? null;
|
|
4803
|
+
if (!pid && projectNameOrId) {
|
|
4804
|
+
// Try as exact ID first, then by name lookup.
|
|
4805
|
+
const byId = centralDb.getProject(projectNameOrId);
|
|
4806
|
+
if (byId) {
|
|
4807
|
+
pid = byId.id;
|
|
4808
|
+
}
|
|
4809
|
+
else {
|
|
4810
|
+
const all = centralDb.getAllProjects();
|
|
4811
|
+
const byName = all.find((p) => p.name === projectNameOrId);
|
|
4812
|
+
if (byName)
|
|
4813
|
+
pid = byName.id;
|
|
4814
|
+
}
|
|
4815
|
+
if (!pid) {
|
|
4816
|
+
console.error(`Project not found: "${projectNameOrId}". Run 'gnosys projects' to list registered projects.`);
|
|
4817
|
+
process.exit(1);
|
|
4818
|
+
}
|
|
4819
|
+
}
|
|
4321
4820
|
if (!pid)
|
|
4322
4821
|
pid = await detectCurrentProject(centralDb, opts.directory || undefined);
|
|
4323
4822
|
if (!pid) {
|
|
@@ -4425,13 +4924,35 @@ program
|
|
|
4425
4924
|
// ─── gnosys status ──────────────────────────────────────────────────────
|
|
4426
4925
|
program
|
|
4427
4926
|
.command("status")
|
|
4428
|
-
.description("Show project status
|
|
4927
|
+
.description("Show project status (--global: all projects, --web: HTML dashboard, --system: memory/LLM health)")
|
|
4429
4928
|
.option("-d, --directory <dir>", "Project directory (auto-detects if omitted)")
|
|
4430
4929
|
.option("-p, --project <id>", "Project ID")
|
|
4431
4930
|
.option("-g, --global", "Show all projects")
|
|
4432
4931
|
.option("-w, --web", "Open the HTML dashboard in the browser")
|
|
4932
|
+
.option("-s, --system", "Show system health (memory count, LLM connectivity, embeddings, archive)")
|
|
4433
4933
|
.option("--json", "Output as JSON")
|
|
4434
4934
|
.action(async (opts) => {
|
|
4935
|
+
// --system delegates to the dashboard formatter (formerly `gnosys dashboard`).
|
|
4936
|
+
if (opts.system) {
|
|
4937
|
+
const { collectDashboardData, formatDashboard, formatDashboardJSON } = await import("./lib/dashboard.js");
|
|
4938
|
+
const resolver = await getResolver();
|
|
4939
|
+
const stores = resolver.getStores();
|
|
4940
|
+
if (stores.length === 0) {
|
|
4941
|
+
console.error("No Gnosys stores found. Run gnosys init first.");
|
|
4942
|
+
process.exit(1);
|
|
4943
|
+
}
|
|
4944
|
+
const cfg = await loadConfig(stores[0].path);
|
|
4945
|
+
let dashDb;
|
|
4946
|
+
try {
|
|
4947
|
+
const db = GnosysDB.openCentral();
|
|
4948
|
+
if (db.isAvailable() && db.isMigrated())
|
|
4949
|
+
dashDb = db;
|
|
4950
|
+
}
|
|
4951
|
+
catch { /* non-fatal */ }
|
|
4952
|
+
const data = await collectDashboardData(resolver, cfg, pkg.version, dashDb);
|
|
4953
|
+
console.log(opts.json ? formatDashboardJSON(data) : formatDashboard(data));
|
|
4954
|
+
return;
|
|
4955
|
+
}
|
|
4435
4956
|
let centralDb = null;
|
|
4436
4957
|
try {
|
|
4437
4958
|
centralDb = GnosysDB.openCentral();
|
|
@@ -4654,7 +5175,7 @@ program
|
|
|
4654
5175
|
// ─── gnosys sandbox start|stop|status ─────────────────────────────────────
|
|
4655
5176
|
const sandboxCmd = program
|
|
4656
5177
|
.command("sandbox")
|
|
4657
|
-
.description("Manage the Gnosys sandbox background process");
|
|
5178
|
+
.description("Manage the Gnosys sandbox — a long-lived background process that holds the SQLite handle so agents can call gnosys.add()/recall() through a tiny helper library instead of paying the MCP roundtrip on every call. Lower latency, lower context cost. Most users don't need this; it's for high-throughput agent workflows.");
|
|
4658
5179
|
sandboxCmd
|
|
4659
5180
|
.command("start")
|
|
4660
5181
|
.description("Start the Gnosys sandbox background process")
|
|
@@ -4737,7 +5258,7 @@ sandboxCmd
|
|
|
4737
5258
|
// ─── gnosys helper generate ───────────────────────────────────────────────
|
|
4738
5259
|
const helperCmd = program
|
|
4739
5260
|
.command("helper")
|
|
4740
|
-
.description("
|
|
5261
|
+
.description("Generate a tiny TypeScript helper library that agents import to talk to the gnosys sandbox directly. Pairs with `gnosys sandbox start` — agents call gnosys.add()/recall() like normal code instead of issuing MCP tool calls. Run `gnosys helper generate` in your agent's project to drop in `gnosys-helper.ts`.");
|
|
4741
5262
|
helperCmd
|
|
4742
5263
|
.command("generate")
|
|
4743
5264
|
.description("Generate a gnosys-helper.ts file in the current directory (or specified directory)")
|