gnosys 5.12.0 → 5.12.2
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 +48 -7
- package/dist/index.js +179 -10
- package/dist/lib/addCommand.js +0 -1
- package/dist/lib/archive.js +0 -2
- package/dist/lib/askCommand.js +1 -1
- package/dist/lib/attachCommand.d.ts +17 -0
- package/dist/lib/attachCommand.js +66 -0
- package/dist/lib/attachments.d.ts +43 -2
- package/dist/lib/attachments.js +81 -2
- package/dist/lib/chat/choose.js +2 -2
- package/dist/lib/clientReadOverlay.js +3 -0
- package/dist/lib/config.d.ts +1 -48
- package/dist/lib/configCommand.js +2 -2
- package/dist/lib/db.d.ts +16 -1
- package/dist/lib/db.js +216 -119
- package/dist/lib/dbWrite.d.ts +1 -1
- package/dist/lib/dearchiveCommand.js +1 -1
- package/dist/lib/docxExtract.js +1 -1
- package/dist/lib/dream.d.ts +8 -0
- package/dist/lib/dream.js +35 -1
- package/dist/lib/dreamLogCommand.js +1 -1
- package/dist/lib/dreamRunLog.d.ts +1 -1
- package/dist/lib/dreamRunLog.js +26 -4
- package/dist/lib/embeddings.js +0 -3
- package/dist/lib/exportProject.d.ts +3 -2
- package/dist/lib/exportProject.js +2 -1
- package/dist/lib/federated.js +1 -1
- package/dist/lib/hybridSearchCommand.js +1 -1
- package/dist/lib/importProject.js +2 -1
- package/dist/lib/llm.js +1 -1
- package/dist/lib/lock.d.ts +1 -1
- package/dist/lib/lock.js +5 -3
- package/dist/lib/migrate.js +0 -1
- package/dist/lib/multimodalIngest.js +1 -1
- package/dist/lib/platform.d.ts +0 -6
- package/dist/lib/platform.js +0 -28
- package/dist/lib/readCommand.js +11 -10
- package/dist/lib/remoteWizard.d.ts +1 -1
- package/dist/lib/remoteWizard.js +4 -4
- package/dist/lib/rulesGen.d.ts +8 -0
- package/dist/lib/rulesGen.js +16 -0
- package/dist/lib/search.d.ts +0 -2
- package/dist/lib/search.js +0 -7
- package/dist/lib/semanticSearchCommand.js +1 -1
- package/dist/lib/setup/sections/providers.js +56 -4
- package/dist/lib/setup/sections/routing.js +42 -5
- package/dist/lib/setup/sections/taskRoutingEditor.d.ts +1 -5
- package/dist/lib/setup/sections/taskRoutingEditor.js +0 -10
- package/dist/lib/setup/ui/header.js +0 -1
- package/dist/lib/setup/ui/status.d.ts +0 -1
- package/dist/lib/setup/ui/status.js +0 -2
- package/dist/lib/setup.d.ts +0 -15
- package/dist/lib/setup.js +13 -158
- package/dist/lib/staleCommand.js +2 -2
- package/dist/lib/syncClient.d.ts +0 -6
- package/dist/lib/syncClient.js +36 -14
- package/dist/lib/syncDoctorCommand.js +2 -2
- package/dist/lib/syncIngest.d.ts +11 -0
- package/dist/lib/syncIngest.js +24 -1
- package/dist/lib/syncIngestStartup.js +2 -2
- package/dist/lib/syncSnapshot.d.ts +2 -0
- package/dist/lib/syncSnapshot.js +4 -0
- package/dist/lib/syncStaging.d.ts +0 -2
- package/dist/lib/syncStaging.js +0 -2
- package/dist/lib/updateCommand.js +1 -1
- package/dist/lib/webBuildCommand.js +1 -1
- package/dist/lib/webIndex.js +0 -1
- package/dist/lib/webIngestCommand.js +1 -1
- package/dist/sandbox/client.js +1 -1
- package/dist/sandbox/manager.js +1 -14
- package/dist/sandbox/server.js +3 -5
- package/package.json +5 -2
package/dist/lib/dream.js
CHANGED
|
@@ -23,7 +23,7 @@ import { createProvider } from "./llm.js";
|
|
|
23
23
|
import { notifyDesktop } from "./desktopNotify.js";
|
|
24
24
|
import { syncConfidenceToDb, auditToDb } from "./dbWrite.js";
|
|
25
25
|
import { logError } from "./log.js";
|
|
26
|
-
import { estimateCost, estimateTokens, fingerprintMemories, memoryWatermark, readDreamState, writeDreamState, } from "./dreamRunLog.js";
|
|
26
|
+
import { estimateCost, acquireDreamLock, estimateTokens, fingerprintMemories, memoryWatermark, readDreamState, writeDreamState, } from "./dreamRunLog.js";
|
|
27
27
|
/** Layer 4 alert threshold: fire desktop notification at this many consecutive provider failures. */
|
|
28
28
|
const DREAM_FAILURE_NOTIFY_THRESHOLD = 3;
|
|
29
29
|
export const DEFAULT_DREAM_CONFIG = {
|
|
@@ -109,6 +109,30 @@ export class GnosysDreamEngine {
|
|
|
109
109
|
phase.durationMs = Date.now() - startedAtMs;
|
|
110
110
|
phase.memoryIdsTouched = Array.from(new Set(phase.memoryIdsTouched));
|
|
111
111
|
phase.estimatedCostUsd = Math.round(phase.estimatedCostUsd * 1_000_000) / 1_000_000;
|
|
112
|
+
this.checkpointFingerprints();
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* v5.12.1 crash safety: persist analyzed fingerprints at every phase
|
|
116
|
+
* boundary, not only in finalize(). A crash mid-run previously lost all
|
|
117
|
+
* pendingFingerprints, so the next run re-analyzed (and re-paid for) the
|
|
118
|
+
* same memory sets and could double-create summaries. Checkpointing only
|
|
119
|
+
* merges fingerprints — lastRunAt / watermarks remain finalize()'s job.
|
|
120
|
+
*/
|
|
121
|
+
checkpointFingerprints() {
|
|
122
|
+
if (Object.keys(this.pendingFingerprints).length === 0)
|
|
123
|
+
return;
|
|
124
|
+
try {
|
|
125
|
+
writeDreamState({
|
|
126
|
+
...this.dreamState,
|
|
127
|
+
analyzedFingerprints: {
|
|
128
|
+
...this.dreamState.analyzedFingerprints,
|
|
129
|
+
...this.pendingFingerprints,
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
// Best-effort: a failed checkpoint only costs re-analysis on resume.
|
|
135
|
+
}
|
|
112
136
|
}
|
|
113
137
|
addTouched(phase, memoryIds) {
|
|
114
138
|
for (const id of memoryIds) {
|
|
@@ -986,6 +1010,15 @@ export class DreamScheduler {
|
|
|
986
1010
|
const idleMs = Date.now() - this.lastActivity;
|
|
987
1011
|
const idleMinutes = idleMs / 60_000;
|
|
988
1012
|
if (idleMinutes >= this.config.idleMinutes) {
|
|
1013
|
+
// v5.12.1: the in-memory `running` flag does not survive a sandbox
|
|
1014
|
+
// restart and cannot see a concurrent manual `gnosys dream`. Tie the
|
|
1015
|
+
// scheduler to the same cross-process file lock the CLI uses.
|
|
1016
|
+
const lock = acquireDreamLock();
|
|
1017
|
+
if (!lock.acquired) {
|
|
1018
|
+
console.error(`[dream] scheduler skipped: ${lock.reason}`);
|
|
1019
|
+
this.lastActivity = Date.now(); // back off a full idle window
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
989
1022
|
this.running = true;
|
|
990
1023
|
try {
|
|
991
1024
|
this.currentDream = this.engine.dream((phase, detail) => {
|
|
@@ -1002,6 +1035,7 @@ export class DreamScheduler {
|
|
|
1002
1035
|
this.running = false;
|
|
1003
1036
|
this.currentDream = null;
|
|
1004
1037
|
this.lastActivity = Date.now(); // Reset idle timer after dream
|
|
1038
|
+
lock.release();
|
|
1005
1039
|
}
|
|
1006
1040
|
}
|
|
1007
1041
|
}
|
|
@@ -8,7 +8,7 @@ export async function runDreamLogCommand(opts, context = {}) {
|
|
|
8
8
|
process.exitCode = 1;
|
|
9
9
|
return;
|
|
10
10
|
}
|
|
11
|
-
const limit = Math.max(1, parseInt(opts.last) || 20);
|
|
11
|
+
const limit = Math.max(1, parseInt(opts.last, 10) || 20);
|
|
12
12
|
const sinceIso = opts.since ? `${opts.since}T00:00:00Z` : undefined;
|
|
13
13
|
const runs = centralDb.getRecentDreamRuns(limit, {
|
|
14
14
|
failuresOnly: !!opts.failuresOnly,
|
|
@@ -88,7 +88,7 @@ export interface DreamReadOptions {
|
|
|
88
88
|
export declare function getDreamRunsPath(): string;
|
|
89
89
|
export declare function getDreamStatePath(): string;
|
|
90
90
|
export declare function getDreamLockPath(): string;
|
|
91
|
-
export declare function acquireDreamLock(): {
|
|
91
|
+
export declare function acquireDreamLock(depth?: number): {
|
|
92
92
|
acquired: true;
|
|
93
93
|
release: () => void;
|
|
94
94
|
} | {
|
package/dist/lib/dreamRunLog.js
CHANGED
|
@@ -26,7 +26,7 @@ export function getDreamStatePath() {
|
|
|
26
26
|
export function getDreamLockPath() {
|
|
27
27
|
return path.join(getGnosysHome(), "dream.lock");
|
|
28
28
|
}
|
|
29
|
-
export function acquireDreamLock() {
|
|
29
|
+
export function acquireDreamLock(depth = 0) {
|
|
30
30
|
const lockPath = getDreamLockPath();
|
|
31
31
|
fs.mkdirSync(path.dirname(lockPath), { recursive: true });
|
|
32
32
|
try {
|
|
@@ -46,17 +46,39 @@ export function acquireDreamLock() {
|
|
|
46
46
|
};
|
|
47
47
|
}
|
|
48
48
|
catch {
|
|
49
|
+
// Bounded retries so stale-lock cleanup can't recurse forever under
|
|
50
|
+
// genuine contention (two processes racing to re-acquire).
|
|
51
|
+
if (depth >= 3) {
|
|
52
|
+
return { acquired: false, reason: "dream lock contention" };
|
|
53
|
+
}
|
|
54
|
+
let raw;
|
|
55
|
+
try {
|
|
56
|
+
raw = fs.readFileSync(lockPath, "utf8");
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// Lock vanished between our create attempt and this read — retry.
|
|
60
|
+
return acquireDreamLock(depth + 1);
|
|
61
|
+
}
|
|
49
62
|
try {
|
|
50
|
-
const raw = fs.readFileSync(lockPath, "utf8");
|
|
51
63
|
const parsed = JSON.parse(raw);
|
|
52
64
|
if (parsed.pid && !isProcessRunning(parsed.pid)) {
|
|
53
65
|
fs.unlinkSync(lockPath);
|
|
54
|
-
return acquireDreamLock();
|
|
66
|
+
return acquireDreamLock(depth + 1);
|
|
55
67
|
}
|
|
56
68
|
return { acquired: false, reason: `dream already running (pid ${parsed.pid || "unknown"})` };
|
|
57
69
|
}
|
|
58
70
|
catch {
|
|
59
|
-
|
|
71
|
+
// v5.12.1: unreadable/corrupt lock (crash mid-write) previously blocked
|
|
72
|
+
// dreaming forever until manual deletion. Treat it as stale: the lock
|
|
73
|
+
// payload is a single tiny write, so a live owner with a corrupt lock
|
|
74
|
+
// is implausible.
|
|
75
|
+
try {
|
|
76
|
+
fs.unlinkSync(lockPath);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// ignore — next acquire retries
|
|
80
|
+
}
|
|
81
|
+
return acquireDreamLock(depth + 1);
|
|
60
82
|
}
|
|
61
83
|
}
|
|
62
84
|
}
|
package/dist/lib/embeddings.js
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
* Embeddings are stored in SQLite as regeneratable sidecar data.
|
|
6
6
|
*/
|
|
7
7
|
// Dynamic import — gracefully handles missing native module (dlopen failures)
|
|
8
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9
8
|
let Database = null;
|
|
10
9
|
try {
|
|
11
10
|
Database = (await import("better-sqlite3")).default;
|
|
@@ -17,10 +16,8 @@ import path from "path";
|
|
|
17
16
|
import fs from "fs/promises";
|
|
18
17
|
import { enableWAL } from "./lock.js";
|
|
19
18
|
const MODEL_NAME = "Xenova/all-MiniLM-L6-v2";
|
|
20
|
-
const EMBEDDING_DIM = 384;
|
|
21
19
|
export class GnosysEmbeddings {
|
|
22
20
|
pipeline = null;
|
|
23
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
24
21
|
db = null;
|
|
25
22
|
storePath;
|
|
26
23
|
modelReady = false;
|
|
@@ -14,9 +14,10 @@ interface BundleManifest {
|
|
|
14
14
|
source_user: string;
|
|
15
15
|
gnosys_version: string;
|
|
16
16
|
}
|
|
17
|
-
/** A memory row with its
|
|
18
|
-
export interface PortableMemory extends Omit<DbMemory, "embedding"> {
|
|
17
|
+
/** A memory row with its binary columns base64-encoded for JSON transport. */
|
|
18
|
+
export interface PortableMemory extends Omit<DbMemory, "embedding" | "attachment_data"> {
|
|
19
19
|
embedding_b64: string | null;
|
|
20
|
+
attachment_data_b64: string | null;
|
|
20
21
|
}
|
|
21
22
|
export interface ProjectBundle {
|
|
22
23
|
manifest: BundleManifest;
|
|
@@ -34,10 +34,11 @@ export function exportProject(db, opts) {
|
|
|
34
34
|
const totalIncludingArchived = db.getMemoriesByProject(opts.projectId, true).length;
|
|
35
35
|
const archivedExcluded = opts.includeArchived ? 0 : totalIncludingArchived - rawMemories.length;
|
|
36
36
|
const memories = rawMemories.map((m) => {
|
|
37
|
-
const { embedding: _embedding, ...rest } = m;
|
|
37
|
+
const { embedding: _embedding, attachment_data: _attachment, ...rest } = m;
|
|
38
38
|
return {
|
|
39
39
|
...rest,
|
|
40
40
|
embedding_b64: m.embedding ? Buffer.from(m.embedding).toString("base64") : null,
|
|
41
|
+
attachment_data_b64: m.attachment_data ? Buffer.from(m.attachment_data).toString("base64") : null,
|
|
41
42
|
};
|
|
42
43
|
});
|
|
43
44
|
const memoryIds = rawMemories.map((m) => m.id);
|
package/dist/lib/federated.js
CHANGED
|
@@ -132,7 +132,7 @@ export function detectAmbiguity(db, query, opts) {
|
|
|
132
132
|
const projectHits = new Map();
|
|
133
133
|
for (const r of results) {
|
|
134
134
|
const mem = db.getMemory(r.id);
|
|
135
|
-
if (!mem
|
|
135
|
+
if (!mem?.project_id)
|
|
136
136
|
continue;
|
|
137
137
|
projectHits.set(mem.project_id, (projectHits.get(mem.project_id) || 0) + 1);
|
|
138
138
|
}
|
|
@@ -67,7 +67,7 @@ export async function runHybridSearchCommand(getResolver, query, opts) {
|
|
|
67
67
|
const embeddings = new GnosysEmbeddings(storePath);
|
|
68
68
|
const hybridSearch = new GnosysHybridSearch(search, embeddings, resolver, storePath);
|
|
69
69
|
const mode = opts.mode;
|
|
70
|
-
const results = await hybridSearch.hybridSearch(query, parseInt(opts.limit), mode);
|
|
70
|
+
const results = await hybridSearch.hybridSearch(query, parseInt(opts.limit, 10), mode);
|
|
71
71
|
if (results.length === 0) {
|
|
72
72
|
outputResult(!!opts.json, { query, mode, results: [] }, () => {
|
|
73
73
|
console.log(`No results for "${query}". Try gnosys reindex to build embeddings.`);
|
|
@@ -20,10 +20,11 @@ export function readBundle(bundlePath) {
|
|
|
20
20
|
return bundle;
|
|
21
21
|
}
|
|
22
22
|
function portableToDbMemory(p) {
|
|
23
|
-
const { embedding_b64, ...rest } = p;
|
|
23
|
+
const { embedding_b64, attachment_data_b64, ...rest } = p;
|
|
24
24
|
return {
|
|
25
25
|
...rest,
|
|
26
26
|
embedding: embedding_b64 ? Buffer.from(embedding_b64, "base64") : null,
|
|
27
|
+
attachment_data: attachment_data_b64 ? Buffer.from(attachment_data_b64, "base64") : null,
|
|
27
28
|
};
|
|
28
29
|
}
|
|
29
30
|
/** Restore a bundle into the central DB. Returns counts and the final project ID. */
|
package/dist/lib/llm.js
CHANGED
|
@@ -487,7 +487,7 @@ export function createProvider(provider, model, config, task) {
|
|
|
487
487
|
}
|
|
488
488
|
case "custom": {
|
|
489
489
|
const customConfig = config.llm.custom;
|
|
490
|
-
if (!customConfig
|
|
490
|
+
if (!customConfig?.baseUrl || !customConfig.model) {
|
|
491
491
|
throw new Error("Custom provider not configured. Set llm.custom.baseUrl and llm.custom.model in gnosys.json, or use: gnosys config set provider custom");
|
|
492
492
|
}
|
|
493
493
|
const apiKey = getCustomApiKey(config);
|
package/dist/lib/lock.d.ts
CHANGED
|
@@ -29,4 +29,4 @@ export declare function acquireWriteLock(storePath: string, operation?: string):
|
|
|
29
29
|
* unboundedly until something triggers a manual checkpoint — we observed
|
|
30
30
|
* 4MB+ WAL files in the wild with no checkpoint cadence in v5.4.0.
|
|
31
31
|
*/
|
|
32
|
-
export declare function enableWAL(db: any): void;
|
|
32
|
+
export declare function enableWAL(db: any, busyTimeoutMs?: number): void;
|
package/dist/lib/lock.js
CHANGED
|
@@ -134,11 +134,13 @@ function isLockStale(lock) {
|
|
|
134
134
|
* unboundedly until something triggers a manual checkpoint — we observed
|
|
135
135
|
* 4MB+ WAL files in the wild with no checkpoint cadence in v5.4.0.
|
|
136
136
|
*/
|
|
137
|
-
|
|
138
|
-
export function enableWAL(db) {
|
|
137
|
+
export function enableWAL(db, busyTimeoutMs = 5000) {
|
|
139
138
|
try {
|
|
140
139
|
db.pragma("journal_mode = WAL");
|
|
141
|
-
|
|
140
|
+
// Single source of truth for the connection's busy timeout — callers on
|
|
141
|
+
// network shares (central DB) pass a longer value (10s) instead of
|
|
142
|
+
// overriding with a second pragma afterwards.
|
|
143
|
+
db.pragma(`busy_timeout = ${Math.max(0, Math.floor(busyTimeoutMs))}`);
|
|
142
144
|
// Auto-checkpoint after every 1000 frames written to WAL. Default is
|
|
143
145
|
// 1000 anyway in newer SQLite, but set explicitly so behavior is
|
|
144
146
|
// predictable across SQLite versions.
|
package/dist/lib/migrate.js
CHANGED
|
@@ -10,7 +10,6 @@ import { GnosysDB, fnv1a } from "./db.js";
|
|
|
10
10
|
import { GnosysStore } from "./store.js";
|
|
11
11
|
import { GnosysArchive } from "./archive.js";
|
|
12
12
|
// Dynamic import for embeddings DB
|
|
13
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
14
13
|
let Database = null;
|
|
15
14
|
try {
|
|
16
15
|
Database = (await import("better-sqlite3")).default;
|
|
@@ -143,7 +143,7 @@ function buildTranscriptChunks(segments, targetSize) {
|
|
|
143
143
|
*/
|
|
144
144
|
export async function ingestFile(options) {
|
|
145
145
|
const startTime = Date.now();
|
|
146
|
-
const { filePath, storePath, mode = "llm", author = "human", authority = "imported", dryRun = false,
|
|
146
|
+
const { filePath, storePath, mode = "llm", author = "human", authority = "imported", dryRun = false, onProgress, } = options;
|
|
147
147
|
// Step 1: Detect file type
|
|
148
148
|
const fileInfo = await detectFileType(filePath);
|
|
149
149
|
// Reject unsupported types early
|
package/dist/lib/platform.d.ts
CHANGED
|
@@ -4,16 +4,10 @@
|
|
|
4
4
|
export type OsFamily = "macos" | "linux" | "windows";
|
|
5
5
|
/** Current OS family for CLI messages and help text. */
|
|
6
6
|
export declare function getOsFamily(): OsFamily;
|
|
7
|
-
/** Primary secure credential store name on this machine. */
|
|
8
|
-
export declare function getSecureStorageLabel(): string;
|
|
9
7
|
/** Short phrase for error messages (setup may still be required on Windows). */
|
|
10
8
|
export declare function getSecureStorageSetupHint(): string;
|
|
11
|
-
/** Order of API key resolution for user-facing help on the current OS. */
|
|
12
|
-
export declare function getApiKeyResolutionOrderText(): string;
|
|
13
9
|
/** Claude Desktop MCP config file path for the current platform. */
|
|
14
10
|
export declare function getClaudeDesktopConfigPath(): string;
|
|
15
|
-
/** Display path with ~ for home (for logs and help). */
|
|
16
|
-
export declare function displayClaudeDesktopConfigPath(): string;
|
|
17
11
|
/** Shell profile file(s) suggested for env vars on this OS. */
|
|
18
12
|
export declare function getShellProfileHint(): string;
|
|
19
13
|
/** Lines shown when user skips API key setup in gnosys setup. */
|
package/dist/lib/platform.js
CHANGED
|
@@ -11,17 +11,6 @@ export function getOsFamily() {
|
|
|
11
11
|
return "windows";
|
|
12
12
|
return "linux";
|
|
13
13
|
}
|
|
14
|
-
/** Primary secure credential store name on this machine. */
|
|
15
|
-
export function getSecureStorageLabel() {
|
|
16
|
-
switch (getOsFamily()) {
|
|
17
|
-
case "macos":
|
|
18
|
-
return "macOS Keychain";
|
|
19
|
-
case "linux":
|
|
20
|
-
return "GNOME Keyring";
|
|
21
|
-
case "windows":
|
|
22
|
-
return "Windows Credential Manager";
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
14
|
/** Short phrase for error messages (setup may still be required on Windows). */
|
|
26
15
|
export function getSecureStorageSetupHint() {
|
|
27
16
|
switch (getOsFamily()) {
|
|
@@ -33,17 +22,6 @@ export function getSecureStorageSetupHint() {
|
|
|
33
22
|
return "your user environment or ~/.config/gnosys/.env (via gnosys setup)";
|
|
34
23
|
}
|
|
35
24
|
}
|
|
36
|
-
/** Order of API key resolution for user-facing help on the current OS. */
|
|
37
|
-
export function getApiKeyResolutionOrderText() {
|
|
38
|
-
switch (getOsFamily()) {
|
|
39
|
-
case "macos":
|
|
40
|
-
return "macOS Keychain, environment variable, then ~/.config/gnosys/.env";
|
|
41
|
-
case "linux":
|
|
42
|
-
return "GNOME Keyring (when available), environment variable, then ~/.config/gnosys/.env";
|
|
43
|
-
case "windows":
|
|
44
|
-
return "environment variable, then ~/.config/gnosys/.env";
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
25
|
/** Claude Desktop MCP config file path for the current platform. */
|
|
48
26
|
export function getClaudeDesktopConfigPath() {
|
|
49
27
|
const home = os.homedir();
|
|
@@ -56,12 +34,6 @@ export function getClaudeDesktopConfigPath() {
|
|
|
56
34
|
}
|
|
57
35
|
return path.join(home, ".config", "Claude", "claude_desktop_config.json");
|
|
58
36
|
}
|
|
59
|
-
/** Display path with ~ for home (for logs and help). */
|
|
60
|
-
export function displayClaudeDesktopConfigPath() {
|
|
61
|
-
const home = os.homedir();
|
|
62
|
-
const p = getClaudeDesktopConfigPath();
|
|
63
|
-
return p.startsWith(home) ? "~" + p.slice(home.length) : p;
|
|
64
|
-
}
|
|
65
37
|
/** Shell profile file(s) suggested for env vars on this OS. */
|
|
66
38
|
export function getShellProfileHint() {
|
|
67
39
|
switch (getOsFamily()) {
|
package/dist/lib/readCommand.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import fs from "fs/promises";
|
|
2
|
-
import { GnosysDB } from "./db.js";
|
|
3
2
|
function outputResult(json, data, humanFn) {
|
|
4
3
|
if (json) {
|
|
5
4
|
console.log(JSON.stringify(data, null, 2));
|
|
@@ -9,11 +8,14 @@ function outputResult(json, data, humanFn) {
|
|
|
9
8
|
}
|
|
10
9
|
}
|
|
11
10
|
export async function runReadCommand(getResolver, memoryPath, opts) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
// v13 client read path: master/snapshot routing + pending-offline-adds overlay,
|
|
12
|
+
// so `gnosys read <id>` sees the same memories as list/discover/search.
|
|
13
|
+
const { resolveClientRead, getMemoryWithOverlay } = await import("./clientReadResolve.js");
|
|
14
|
+
const resolved = resolveClientRead();
|
|
15
|
+
if (resolved) {
|
|
16
|
+
try {
|
|
17
|
+
const dbMem = getMemoryWithOverlay(resolved, memoryPath);
|
|
18
|
+
if (dbMem) {
|
|
17
19
|
const tags = dbMem.tags || "[]";
|
|
18
20
|
const headerLines = [
|
|
19
21
|
`---`,
|
|
@@ -42,12 +44,11 @@ export async function runReadCommand(getResolver, memoryPath, opts) {
|
|
|
42
44
|
});
|
|
43
45
|
return;
|
|
44
46
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
resolved.release();
|
|
48
50
|
}
|
|
49
51
|
}
|
|
50
|
-
centralDb.close();
|
|
51
52
|
const resolver = await getResolver();
|
|
52
53
|
const memory = await resolver.readMemory(memoryPath);
|
|
53
54
|
if (!memory) {
|
|
@@ -24,7 +24,7 @@ export declare function configureFromPath(centralDb: GnosysDB, remotePath: strin
|
|
|
24
24
|
migrate?: boolean;
|
|
25
25
|
role?: MultiMachineRole;
|
|
26
26
|
}): Promise<boolean>;
|
|
27
|
-
export { stagingDirForMachine, clientPresencePath } from "./syncStaging.js";
|
|
27
|
+
export { machineStagingDir as stagingDirForMachine, clientPresencePath } from "./syncStaging.js";
|
|
28
28
|
export declare const __test: {
|
|
29
29
|
matchesTypedPhrase: typeof matchesTypedPhrase;
|
|
30
30
|
detectClonedStagingPresence: typeof detectClonedStagingPresence;
|
package/dist/lib/remoteWizard.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* - Reconfigure: change path, re-validate, or disconnect (when already configured)
|
|
7
7
|
*/
|
|
8
8
|
import { randomUUID } from "crypto";
|
|
9
|
-
import { existsSync, mkdirSync, renameSync } from "fs";
|
|
9
|
+
import { existsSync, mkdirSync, renameSync, } from "fs";
|
|
10
10
|
import * as path from "path";
|
|
11
11
|
import { createInterface } from "readline/promises";
|
|
12
12
|
import { GnosysDB } from "./db.js";
|
|
@@ -16,7 +16,7 @@ import { getGnosysHome } from "./paths.js";
|
|
|
16
16
|
import { atomicWriteFileSync } from "./atomicWrite.js";
|
|
17
17
|
import { readMasterMarker, writeMasterMarker } from "./masterLease.js";
|
|
18
18
|
import { checkMasterPathLocalDisk, LOCAL_DISK_ACK_PHRASE, } from "./localDiskCheck.js";
|
|
19
|
-
import {
|
|
19
|
+
import { clientPresencePath, machineStagingDir } from "./syncStaging.js";
|
|
20
20
|
import { safeQuestion } from "./setup/ui/safePrompt.js";
|
|
21
21
|
import { Spinner } from "./setup/ui/spinner.js";
|
|
22
22
|
import { printStatus } from "./setup/ui/status.js";
|
|
@@ -500,10 +500,10 @@ export async function configureFromPath(centralDb, remotePath, opts = {}) {
|
|
|
500
500
|
}
|
|
501
501
|
return true;
|
|
502
502
|
}
|
|
503
|
-
export { stagingDirForMachine, clientPresencePath } from "./syncStaging.js";
|
|
503
|
+
export { machineStagingDir as stagingDirForMachine, clientPresencePath } from "./syncStaging.js";
|
|
504
504
|
export const __test = {
|
|
505
505
|
matchesTypedPhrase,
|
|
506
506
|
detectClonedStagingPresence,
|
|
507
|
-
stagingDirForMachine,
|
|
507
|
+
stagingDirForMachine: machineStagingDir,
|
|
508
508
|
clientPresencePath,
|
|
509
509
|
};
|
package/dist/lib/rulesGen.d.ts
CHANGED
|
@@ -47,6 +47,14 @@ export declare function syncRules(centralDb: GnosysDB, projectDir: string, agent
|
|
|
47
47
|
* If target is "global", syncs to ~/.claude/CLAUDE.md.
|
|
48
48
|
*/
|
|
49
49
|
export declare function syncToTarget(centralDb: GnosysDB, projectDir: string, target: string, projectId: string | null): Promise<RulesGenResult[]>;
|
|
50
|
+
/**
|
|
51
|
+
* Remove the GNOSYS block from every known agent rules file in a project —
|
|
52
|
+
* the uninstall counterpart of syncToTarget (v5.12.1: wired into
|
|
53
|
+
* `gnosys cleanup --rules`). Checks every known target rather than only
|
|
54
|
+
* detected ones, so leftovers survive even if the agent dir was removed.
|
|
55
|
+
* Returns the relative paths actually cleaned.
|
|
56
|
+
*/
|
|
57
|
+
export declare function removeRulesFromProject(projectDir: string): Promise<string[]>;
|
|
50
58
|
/**
|
|
51
59
|
* Remove the GNOSYS block from a rules file (cleanup).
|
|
52
60
|
*/
|
package/dist/lib/rulesGen.js
CHANGED
|
@@ -254,6 +254,22 @@ export async function syncToTarget(centralDb, projectDir, target, projectId) {
|
|
|
254
254
|
}
|
|
255
255
|
return results;
|
|
256
256
|
}
|
|
257
|
+
/**
|
|
258
|
+
* Remove the GNOSYS block from every known agent rules file in a project —
|
|
259
|
+
* the uninstall counterpart of syncToTarget (v5.12.1: wired into
|
|
260
|
+
* `gnosys cleanup --rules`). Checks every known target rather than only
|
|
261
|
+
* detected ones, so leftovers survive even if the agent dir was removed.
|
|
262
|
+
* Returns the relative paths actually cleaned.
|
|
263
|
+
*/
|
|
264
|
+
export async function removeRulesFromProject(projectDir) {
|
|
265
|
+
const cleaned = [];
|
|
266
|
+
for (const relPath of Object.values(TARGET_PATHS)) {
|
|
267
|
+
const filePath = path.join(projectDir, relPath);
|
|
268
|
+
if (await removeRulesBlock(filePath))
|
|
269
|
+
cleaned.push(relPath);
|
|
270
|
+
}
|
|
271
|
+
return cleaned;
|
|
272
|
+
}
|
|
257
273
|
/**
|
|
258
274
|
* Remove the GNOSYS block from a rules file (cleanup).
|
|
259
275
|
*/
|
package/dist/lib/search.d.ts
CHANGED
package/dist/lib/search.js
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* FTS5-based search and discovery across all Gnosys stores.
|
|
4
4
|
*/
|
|
5
5
|
// Dynamic import — gracefully handles missing native module (dlopen failures)
|
|
6
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
7
6
|
let Database = null;
|
|
8
7
|
try {
|
|
9
8
|
Database = (await import("better-sqlite3")).default;
|
|
@@ -13,12 +12,8 @@ catch {
|
|
|
13
12
|
}
|
|
14
13
|
import path from "path";
|
|
15
14
|
export class GnosysSearch {
|
|
16
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
17
15
|
db = null;
|
|
18
|
-
storePath;
|
|
19
|
-
available = false;
|
|
20
16
|
constructor(storePath) {
|
|
21
|
-
this.storePath = storePath;
|
|
22
17
|
if (!Database) {
|
|
23
18
|
// Native module not available — search features disabled
|
|
24
19
|
return;
|
|
@@ -34,7 +29,6 @@ export class GnosysSearch {
|
|
|
34
29
|
this.initSchema();
|
|
35
30
|
// Smoke-test: insert + delete to confirm journal ops work
|
|
36
31
|
this.db.exec("CREATE TABLE IF NOT EXISTS _write_test (v INTEGER); INSERT INTO _write_test VALUES (1); DELETE FROM _write_test; DROP TABLE _write_test;");
|
|
37
|
-
this.available = true;
|
|
38
32
|
}
|
|
39
33
|
catch {
|
|
40
34
|
// Fallback to in-memory (works everywhere, rebuilt on each start)
|
|
@@ -44,7 +38,6 @@ export class GnosysSearch {
|
|
|
44
38
|
catch { /* ignore */ }
|
|
45
39
|
this.db = new Database(":memory:");
|
|
46
40
|
this.initSchema();
|
|
47
|
-
this.available = true;
|
|
48
41
|
}
|
|
49
42
|
}
|
|
50
43
|
initSchema() {
|
|
@@ -24,7 +24,7 @@ export async function runSemanticSearchCommand(getResolver, query, opts) {
|
|
|
24
24
|
const { GnosysHybridSearch } = await import("./hybridSearch.js");
|
|
25
25
|
const embeddings = new GnosysEmbeddings(storePath);
|
|
26
26
|
const hybridSearch = new GnosysHybridSearch(search, embeddings, resolver, storePath);
|
|
27
|
-
const results = await hybridSearch.hybridSearch(query, parseInt(opts.limit), "semantic");
|
|
27
|
+
const results = await hybridSearch.hybridSearch(query, parseInt(opts.limit, 10), "semantic");
|
|
28
28
|
outputResult(!!opts.json, {
|
|
29
29
|
query,
|
|
30
30
|
count: results.length,
|
|
@@ -129,14 +129,67 @@ async function manageProviderKeys(rl, storePath, cfg, provider) {
|
|
|
129
129
|
}
|
|
130
130
|
console.log("");
|
|
131
131
|
}
|
|
132
|
+
const defaultAction = slots.length > 0 ? 2 : 0; // when they have a key, default to "make default + model"
|
|
132
133
|
const choice = await askChoice(rl, "Actions", [
|
|
133
134
|
slots.length === 0 ? "Add API key (global)" : "Rotate global API key",
|
|
134
135
|
"Delete one stored key…",
|
|
136
|
+
"Use this provider as default for everything + pick model",
|
|
135
137
|
"Back",
|
|
136
|
-
],
|
|
137
|
-
if (choice ===
|
|
138
|
+
], defaultAction);
|
|
139
|
+
if (choice === 3) {
|
|
138
140
|
return changed;
|
|
139
141
|
}
|
|
142
|
+
if (choice === 2) {
|
|
143
|
+
// "Use this provider as default for everything + pick model"
|
|
144
|
+
// Set it as the global default provider
|
|
145
|
+
const current = await loadConfig(storePath);
|
|
146
|
+
await updateConfig(storePath, {
|
|
147
|
+
llm: {
|
|
148
|
+
...current.llm,
|
|
149
|
+
defaultProvider: provider,
|
|
150
|
+
},
|
|
151
|
+
// Clear per-task overrides so this truly becomes the single default for everything
|
|
152
|
+
taskModels: {},
|
|
153
|
+
});
|
|
154
|
+
printStatus("ok", `default provider set to ${provider} (all tasks now default to it)`);
|
|
155
|
+
// Immediately let them pick a model for it (using the same picker the main flow uses)
|
|
156
|
+
try {
|
|
157
|
+
const { fetchDynamicModels } = await import("../../setup.js");
|
|
158
|
+
const { pickModel } = await import("../../setup.js");
|
|
159
|
+
const dynamicModels = await fetchDynamicModels();
|
|
160
|
+
const tiers = dynamicModels[provider] ?? [];
|
|
161
|
+
let chosenModel;
|
|
162
|
+
if (tiers.length > 0) {
|
|
163
|
+
chosenModel = await pickModel(rl, provider, dynamicModels, `Default model for ${provider} (used for all tasks)`, current.llm[provider]?.model);
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
chosenModel = await ask(rl, `Enter default model name for ${provider}: `);
|
|
167
|
+
}
|
|
168
|
+
if (chosenModel) {
|
|
169
|
+
const after = await loadConfig(storePath);
|
|
170
|
+
await updateConfig(storePath, {
|
|
171
|
+
llm: {
|
|
172
|
+
...after.llm,
|
|
173
|
+
[provider]: {
|
|
174
|
+
...(after.llm[provider] || {}),
|
|
175
|
+
model: chosenModel,
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
printStatus("ok", `default model set · ${provider} / ${chosenModel}`);
|
|
180
|
+
printStatus("progress", "all tasks will now default to this provider + model", "use task routing for per-task overrides");
|
|
181
|
+
changed = true;
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
printStatus("progress", "model left unchanged — you can set it from task routing");
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
catch (_err) {
|
|
188
|
+
printStatus("warn", "could not pick model right now", "you can set a default model from the main setup → task routing");
|
|
189
|
+
}
|
|
190
|
+
cfg = await loadConfig(storePath);
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
140
193
|
if (choice === 0) {
|
|
141
194
|
const service = apiKeyServiceName(provider, "global");
|
|
142
195
|
console.log("");
|
|
@@ -188,7 +241,6 @@ async function manageProviderKeys(rl, storePath, cfg, provider) {
|
|
|
188
241
|
printStatus("fail", "delete failed or entry not found");
|
|
189
242
|
}
|
|
190
243
|
}
|
|
191
|
-
continue;
|
|
192
244
|
}
|
|
193
245
|
}
|
|
194
246
|
}
|
|
@@ -207,7 +259,7 @@ export async function runProvidersSetup(opts) {
|
|
|
207
259
|
console.log("");
|
|
208
260
|
console.log(Header(["gnosys", "setup", "providers"]));
|
|
209
261
|
console.log("");
|
|
210
|
-
console.log(Title("Providers", "API keys live here
|
|
262
|
+
console.log(Title("Providers", "API keys live here. Set a global default (or per-task models) from the main setup menu."));
|
|
211
263
|
console.log("");
|
|
212
264
|
for (let i = 0; i < menuProviders.length; i++) {
|
|
213
265
|
const p = menuProviders[i];
|