gnosys 5.11.0 → 5.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +4 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -2
- package/dist/lib/ask.js +2 -1
- package/dist/lib/ingest.js +2 -1
- package/dist/lib/mcpClientConfig.js +2 -14
- package/dist/lib/platform.d.ts +24 -0
- package/dist/lib/platform.js +114 -0
- package/dist/lib/remote.d.ts +4 -0
- package/dist/lib/remote.js +21 -1
- package/dist/lib/remoteWizard.d.ts +1 -1
- package/dist/lib/remoteWizard.js +124 -6
- package/dist/lib/setup.js +43 -45
- package/dist/lib/upgrade.d.ts +4 -1
- package/dist/lib/upgrade.js +15 -2
- package/dist/lib/videoExtract.js +2 -4
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -26,6 +26,7 @@ import { loadConfig, generateConfigTemplate, DEFAULT_CONFIG, writeConfig, resolv
|
|
|
26
26
|
import { getLLMProvider, isProviderAvailable } from "./lib/llm.js";
|
|
27
27
|
import { GnosysDB } from "./lib/db.js";
|
|
28
28
|
import { logError } from "./lib/log.js";
|
|
29
|
+
import { getSecureStorageSetupHint } from "./lib/platform.js";
|
|
29
30
|
import { createProjectIdentity, readProjectIdentity, findProjectIdentity, migrateProject } from "./lib/projectIdentity.js";
|
|
30
31
|
import { setPreference, getPreference, getAllPreferences, deletePreference, KNOWN_PREFERENCE_KEYS, suggestPreferenceKey } from "./lib/preferences.js";
|
|
31
32
|
import { syncToTarget } from "./lib/rulesGen.js";
|
|
@@ -2836,7 +2837,7 @@ program
|
|
|
2836
2837
|
const envVar = envVarMap[providerName];
|
|
2837
2838
|
if (envVar) {
|
|
2838
2839
|
console.error(`No LLM provider available. Configured default is "${providerName}" but its key wasn't found. ` +
|
|
2839
|
-
`Set ${envVar}, run 'gnosys setup' to store one in
|
|
2840
|
+
`Set ${envVar}, run 'gnosys setup' to store one in ${getSecureStorageSetupHint()}, or add llm.${providerName}.apiKey to gnosys.json.`);
|
|
2840
2841
|
}
|
|
2841
2842
|
else {
|
|
2842
2843
|
console.error(`No LLM provider available. Provider "${providerName}" is not reachable. Run 'gnosys setup' to configure one.`);
|
|
@@ -4460,7 +4461,8 @@ program
|
|
|
4460
4461
|
setInterval(runMaintenance, SIX_HOURS);
|
|
4461
4462
|
console.error("[maintenance] Background maintenance enabled (every 6 hours)");
|
|
4462
4463
|
}
|
|
4463
|
-
await import("./index.js");
|
|
4464
|
+
const { startMcpServer } = await import("./index.js");
|
|
4465
|
+
await startMcpServer();
|
|
4464
4466
|
});
|
|
4465
4467
|
// ─── gnosys recall ───────────────────────────────────────────────────────
|
|
4466
4468
|
program
|
package/dist/index.d.ts
CHANGED
|
@@ -13,3 +13,5 @@ export declare function registerCapabilities(s: McpServer): void;
|
|
|
13
13
|
* need any of these should `await ensureHeavyDeps()` first.
|
|
14
14
|
*/
|
|
15
15
|
export declare function ensureHeavyDeps(): Promise<void>;
|
|
16
|
+
/** Start the MCP server (stdio or http). Called by `gnosys serve` and when invoked as `gnosys-mcp`. */
|
|
17
|
+
export declare function startMcpServer(): Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -2955,7 +2955,8 @@ async function initHeavyDeps() {
|
|
|
2955
2955
|
heavyDepsReadyResolve?.();
|
|
2956
2956
|
}
|
|
2957
2957
|
// ─── Start the server ────────────────────────────────────────────────────
|
|
2958
|
-
|
|
2958
|
+
/** Start the MCP server (stdio or http). Called by `gnosys serve` and when invoked as `gnosys-mcp`. */
|
|
2959
|
+
export async function startMcpServer() {
|
|
2959
2960
|
// v5.7.1 (#15): start the upgrade-marker watcher BEFORE anything else.
|
|
2960
2961
|
// If `gnosys upgrade` was run on this machine while the MCP was idle,
|
|
2961
2962
|
// pick that up immediately instead of serving stale tool handlers.
|
|
@@ -3093,7 +3094,7 @@ async function main() {
|
|
|
3093
3094
|
}
|
|
3094
3095
|
const invokedAsScript = !!process.argv[1] && fileURLToPath(import.meta.url) === path.resolve(process.argv[1]);
|
|
3095
3096
|
if (invokedAsScript) {
|
|
3096
|
-
|
|
3097
|
+
startMcpServer().catch((err) => {
|
|
3097
3098
|
console.error("Fatal error:", err);
|
|
3098
3099
|
process.exit(1);
|
|
3099
3100
|
});
|
package/dist/lib/ask.js
CHANGED
|
@@ -13,6 +13,7 @@ import { getLLMProvider } from "./llm.js";
|
|
|
13
13
|
import { GnosysArchive } from "./archive.js";
|
|
14
14
|
import { GnosysMaintenanceEngine } from "./maintenance.js";
|
|
15
15
|
import { auditLog } from "./audit.js";
|
|
16
|
+
import { getSecureStorageSetupHint } from "./platform.js";
|
|
16
17
|
const __filename = fileURLToPath(import.meta.url);
|
|
17
18
|
const __dirname = path.dirname(__filename);
|
|
18
19
|
/**
|
|
@@ -132,7 +133,7 @@ export class GnosysAsk {
|
|
|
132
133
|
}
|
|
133
134
|
if (envVar) {
|
|
134
135
|
throw new Error(`gnosys_ask requires an LLM. Configured default provider "${providerName}" has no key. ` +
|
|
135
|
-
`Set ${envVar} in your shell, run 'gnosys setup' to store one in
|
|
136
|
+
`Set ${envVar} in your shell, run 'gnosys setup' to store one in ${getSecureStorageSetupHint()}, or add llm.${providerName}.apiKey to gnosys.json.`);
|
|
136
137
|
}
|
|
137
138
|
throw new Error(`gnosys_ask requires an LLM. Provider "${providerName}" is not available. Run 'gnosys setup' to configure one.`);
|
|
138
139
|
}
|
package/dist/lib/ingest.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { DEFAULT_CONFIG } from "./config.js";
|
|
7
7
|
import { getLLMProvider } from "./llm.js";
|
|
8
|
+
import { getSetupStorageBullet } from "./platform.js";
|
|
8
9
|
export class GnosysIngestion {
|
|
9
10
|
provider = null;
|
|
10
11
|
tagRegistry;
|
|
@@ -73,7 +74,7 @@ export class GnosysIngestion {
|
|
|
73
74
|
lines.push(`Make sure ${providerName} is running locally (gnosys status --system will probe it).`, `Or use gnosys_add_structured for direct memory writes (no LLM needed).`);
|
|
74
75
|
}
|
|
75
76
|
else if (envVar) {
|
|
76
|
-
lines.push(`Configure a key for ${providerName} via one of these methods:`,
|
|
77
|
+
lines.push(`Configure a key for ${providerName} via one of these methods:`, getSetupStorageBullet(), ` • Set ${envVar} in your shell profile`, ` • Edit llm.${providerName}.apiKey in gnosys.json`, "", `Or use gnosys_add_structured for direct memory writes (no LLM needed).`);
|
|
77
78
|
}
|
|
78
79
|
else {
|
|
79
80
|
lines.push(`Switch to a different default provider with: gnosys config set provider <name>`, `Or use gnosys_add_structured for direct memory writes (no LLM needed).`);
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import fs from "fs/promises";
|
|
10
10
|
import path from "path";
|
|
11
|
-
import
|
|
11
|
+
import { getClaudeDesktopConfigPath } from "./platform.js";
|
|
12
12
|
/** The MCP server entry for a remote (HTTP/URL) gnosys server. */
|
|
13
13
|
export function remoteMcpEntry(opts) {
|
|
14
14
|
return {
|
|
@@ -16,18 +16,6 @@ export function remoteMcpEntry(opts) {
|
|
|
16
16
|
...(opts.token ? { headers: { Authorization: `Bearer ${opts.token}` } } : {}),
|
|
17
17
|
};
|
|
18
18
|
}
|
|
19
|
-
/** Platform-specific Claude Desktop config path (mirrors setup.ts). */
|
|
20
|
-
function claudeDesktopConfigPath() {
|
|
21
|
-
const home = os.homedir();
|
|
22
|
-
if (process.platform === "darwin") {
|
|
23
|
-
return path.join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
24
|
-
}
|
|
25
|
-
if (process.platform === "win32") {
|
|
26
|
-
const appData = process.env.APPDATA || path.join(home, "AppData", "Roaming");
|
|
27
|
-
return path.join(appData, "Claude", "claude_desktop_config.json");
|
|
28
|
-
}
|
|
29
|
-
return path.join(home, ".config", "Claude", "claude_desktop_config.json");
|
|
30
|
-
}
|
|
31
19
|
/** Merge a `gnosys` entry into a JSON file's `mcpServers` map (create if absent). */
|
|
32
20
|
export async function mergeJsonMcpServer(file, entry) {
|
|
33
21
|
let config = {};
|
|
@@ -51,7 +39,7 @@ export async function writeCursorRemote(projectDir, opts) {
|
|
|
51
39
|
}
|
|
52
40
|
/** Write the remote entry into the Claude Desktop config. Returns the path. */
|
|
53
41
|
async function writeClaudeDesktopRemote(opts) {
|
|
54
|
-
const file =
|
|
42
|
+
const file = getClaudeDesktopConfigPath();
|
|
55
43
|
await mergeJsonMcpServer(file, remoteMcpEntry(opts));
|
|
56
44
|
return file;
|
|
57
45
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-platform paths and user-facing hints (macOS, Linux, Windows).
|
|
3
|
+
*/
|
|
4
|
+
export type OsFamily = "macos" | "linux" | "windows";
|
|
5
|
+
/** Current OS family for CLI messages and help text. */
|
|
6
|
+
export declare function getOsFamily(): OsFamily;
|
|
7
|
+
/** Primary secure credential store name on this machine. */
|
|
8
|
+
export declare function getSecureStorageLabel(): string;
|
|
9
|
+
/** Short phrase for error messages (setup may still be required on Windows). */
|
|
10
|
+
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
|
+
/** Claude Desktop MCP config file path for the current platform. */
|
|
14
|
+
export declare function getClaudeDesktopConfigPath(): string;
|
|
15
|
+
/** Display path with ~ for home (for logs and help). */
|
|
16
|
+
export declare function displayClaudeDesktopConfigPath(): string;
|
|
17
|
+
/** Shell profile file(s) suggested for env vars on this OS. */
|
|
18
|
+
export declare function getShellProfileHint(): string;
|
|
19
|
+
/** Lines shown when user skips API key setup in gnosys setup. */
|
|
20
|
+
export declare function getApiKeySkipHints(envVarName: string, provider: string): string[];
|
|
21
|
+
/** ffmpeg install instructions (all platforms, for errors). */
|
|
22
|
+
export declare function formatFfmpegInstallHint(): string;
|
|
23
|
+
/** Ingest / LLM missing-key helper bullet for gnosys setup. */
|
|
24
|
+
export declare function getSetupStorageBullet(): string;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-platform paths and user-facing hints (macOS, Linux, Windows).
|
|
3
|
+
*/
|
|
4
|
+
import os from "os";
|
|
5
|
+
import path from "path";
|
|
6
|
+
/** Current OS family for CLI messages and help text. */
|
|
7
|
+
export function getOsFamily() {
|
|
8
|
+
if (process.platform === "darwin")
|
|
9
|
+
return "macos";
|
|
10
|
+
if (process.platform === "win32")
|
|
11
|
+
return "windows";
|
|
12
|
+
return "linux";
|
|
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
|
+
/** Short phrase for error messages (setup may still be required on Windows). */
|
|
26
|
+
export function getSecureStorageSetupHint() {
|
|
27
|
+
switch (getOsFamily()) {
|
|
28
|
+
case "macos":
|
|
29
|
+
return "the macOS Keychain (via gnosys setup)";
|
|
30
|
+
case "linux":
|
|
31
|
+
return "GNOME Keyring (via gnosys setup, when secret-tool is available)";
|
|
32
|
+
case "windows":
|
|
33
|
+
return "your user environment or ~/.config/gnosys/.env (via gnosys setup)";
|
|
34
|
+
}
|
|
35
|
+
}
|
|
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
|
+
/** Claude Desktop MCP config file path for the current platform. */
|
|
48
|
+
export function getClaudeDesktopConfigPath() {
|
|
49
|
+
const home = os.homedir();
|
|
50
|
+
if (process.platform === "darwin") {
|
|
51
|
+
return path.join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
52
|
+
}
|
|
53
|
+
if (process.platform === "win32") {
|
|
54
|
+
const appData = process.env.APPDATA || path.join(home, "AppData", "Roaming");
|
|
55
|
+
return path.join(appData, "Claude", "claude_desktop_config.json");
|
|
56
|
+
}
|
|
57
|
+
return path.join(home, ".config", "Claude", "claude_desktop_config.json");
|
|
58
|
+
}
|
|
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
|
+
/** Shell profile file(s) suggested for env vars on this OS. */
|
|
66
|
+
export function getShellProfileHint() {
|
|
67
|
+
switch (getOsFamily()) {
|
|
68
|
+
case "macos": {
|
|
69
|
+
const shell = path.basename(process.env.SHELL ?? "zsh");
|
|
70
|
+
return shell === "bash" ? "~/.bash_profile or ~/.bashrc" : "~/.zshrc";
|
|
71
|
+
}
|
|
72
|
+
case "linux": {
|
|
73
|
+
const shell = path.basename(process.env.SHELL ?? "bash");
|
|
74
|
+
return shell === "zsh" ? "~/.zshrc" : "~/.bashrc";
|
|
75
|
+
}
|
|
76
|
+
case "windows":
|
|
77
|
+
return "%USERPROFILE%\\Documents\\PowerShell\\Microsoft.PowerShell_profile.ps1 (or System Properties → Environment Variables)";
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/** Lines shown when user skips API key setup in gnosys setup. */
|
|
81
|
+
export function getApiKeySkipHints(envVarName, provider) {
|
|
82
|
+
const hints = [];
|
|
83
|
+
const profile = getShellProfileHint();
|
|
84
|
+
if (getOsFamily() === "macos") {
|
|
85
|
+
hints.push(`macOS Keychain: security add-generic-password -a "$USER" -s "${envVarName}" -w "key" -U`);
|
|
86
|
+
}
|
|
87
|
+
if (getOsFamily() === "linux") {
|
|
88
|
+
hints.push(`GNOME Keyring: printf '%s' 'key' | secret-tool store --label="Gnosys ${provider}" service gnosys account ${envVarName}`);
|
|
89
|
+
}
|
|
90
|
+
if (getOsFamily() === "windows") {
|
|
91
|
+
hints.push(`PowerShell profile: [Environment]::SetEnvironmentVariable("${envVarName}", "key", "User")`);
|
|
92
|
+
}
|
|
93
|
+
hints.push(`Shell profile: echo 'export ${envVarName}=key' >> ${profile}`);
|
|
94
|
+
hints.push(`Dotenv file: echo '${envVarName}=key' >> ~/.config/gnosys/.env`);
|
|
95
|
+
return hints;
|
|
96
|
+
}
|
|
97
|
+
/** ffmpeg install instructions (all platforms, for errors). */
|
|
98
|
+
export function formatFfmpegInstallHint() {
|
|
99
|
+
return ("Install it with:\n" +
|
|
100
|
+
" macOS: brew install ffmpeg\n" +
|
|
101
|
+
" Linux: sudo apt install ffmpeg (Debian/Ubuntu) or your distro package manager\n" +
|
|
102
|
+
" Windows: winget install FFmpeg (or choco install ffmpeg)");
|
|
103
|
+
}
|
|
104
|
+
/** Ingest / LLM missing-key helper bullet for gnosys setup. */
|
|
105
|
+
export function getSetupStorageBullet() {
|
|
106
|
+
switch (getOsFamily()) {
|
|
107
|
+
case "macos":
|
|
108
|
+
return " • gnosys setup — interactive (recommended; stores in macOS Keychain)";
|
|
109
|
+
case "linux":
|
|
110
|
+
return " • gnosys setup — interactive (recommended; stores in GNOME Keyring when available)";
|
|
111
|
+
case "windows":
|
|
112
|
+
return " • gnosys setup — interactive (recommended; env var or ~/.config/gnosys/.env)";
|
|
113
|
+
}
|
|
114
|
+
}
|
package/dist/lib/remote.d.ts
CHANGED
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { GnosysDB, type DbMemory } from "./db.js";
|
|
13
13
|
import type { ProgressCallback } from "./progress.js";
|
|
14
|
+
/** Resolve the configured remote directory for this machine (machine.json, then legacy meta). */
|
|
15
|
+
export declare function getConfiguredRemotePath(localDb: GnosysDB): string | null;
|
|
16
|
+
/** Stop using a remote on this machine; does not delete the remote database files. */
|
|
17
|
+
export declare function clearRemoteSyncConfig(localDb: GnosysDB): void;
|
|
14
18
|
interface ConflictInfo {
|
|
15
19
|
memoryId: string;
|
|
16
20
|
title: string;
|
package/dist/lib/remote.js
CHANGED
|
@@ -13,7 +13,27 @@ import { existsSync, statSync, mkdirSync, writeFileSync, unlinkSync } from "fs";
|
|
|
13
13
|
import os from "os";
|
|
14
14
|
import * as path from "path";
|
|
15
15
|
import { GnosysDB } from "./db.js";
|
|
16
|
-
import { readMachineConfig } from "./machineConfig.js";
|
|
16
|
+
import { readMachineConfig, writeMachineConfig } from "./machineConfig.js";
|
|
17
|
+
const META_REMOTE_PATH = "remote_path";
|
|
18
|
+
const META_REMOTE_MODE = "remote_mode";
|
|
19
|
+
/** Resolve the configured remote directory for this machine (machine.json, then legacy meta). */
|
|
20
|
+
export function getConfiguredRemotePath(localDb) {
|
|
21
|
+
const mc = readMachineConfig();
|
|
22
|
+
if (mc?.remote?.enabled && mc.remote.path)
|
|
23
|
+
return mc.remote.path;
|
|
24
|
+
const fromMeta = localDb.getMeta(META_REMOTE_PATH);
|
|
25
|
+
return fromMeta || null;
|
|
26
|
+
}
|
|
27
|
+
/** Stop using a remote on this machine; does not delete the remote database files. */
|
|
28
|
+
export function clearRemoteSyncConfig(localDb) {
|
|
29
|
+
localDb.setMeta(META_REMOTE_PATH, "");
|
|
30
|
+
localDb.deleteMeta(META_REMOTE_MODE);
|
|
31
|
+
const mc = readMachineConfig();
|
|
32
|
+
if (mc) {
|
|
33
|
+
mc.remote = { enabled: false };
|
|
34
|
+
writeMachineConfig(mc);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
17
37
|
// ─── Validation ─────────────────────────────────────────────────────────
|
|
18
38
|
/**
|
|
19
39
|
* Validate that a directory is suitable for hosting the remote gnosys.db.
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* 3. Join existing — second machine joining a remote that already has data
|
|
8
8
|
*/
|
|
9
9
|
import { type Interface } from "readline/promises";
|
|
10
|
-
import
|
|
10
|
+
import { GnosysDB } from "./db.js";
|
|
11
11
|
export declare function runConfigureWizard(centralDb: GnosysDB, externalRl?: Interface): Promise<boolean>;
|
|
12
12
|
export declare function configureFromPath(centralDb: GnosysDB, remotePath: string, opts?: {
|
|
13
13
|
migrate?: boolean;
|
package/dist/lib/remoteWizard.js
CHANGED
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
import { readdirSync, statSync } from "fs";
|
|
10
10
|
import * as path from "path";
|
|
11
11
|
import { createInterface } from "readline/promises";
|
|
12
|
-
import {
|
|
12
|
+
import { GnosysDB } from "./db.js";
|
|
13
|
+
import { RemoteSync, validateLocation, getConfiguredRemotePath, clearRemoteSyncConfig, } from "./remote.js";
|
|
13
14
|
import { safeQuestion } from "./setup/ui/safePrompt.js";
|
|
14
15
|
import { Spinner } from "./setup/ui/spinner.js";
|
|
15
16
|
import { printStatus } from "./setup/ui/status.js";
|
|
@@ -148,7 +149,7 @@ export async function runConfigureWizard(centralDb, externalRl) {
|
|
|
148
149
|
const choice = await askChoice(rl, "What would you like to do?", [
|
|
149
150
|
{ key: "1", label: "Change remote location" },
|
|
150
151
|
{ key: "2", label: "Re-validate current remote" },
|
|
151
|
-
{ key: "3", label: "Disconnect remote (
|
|
152
|
+
{ key: "3", label: "Disconnect remote (local-only — warns if sync is needed)" },
|
|
152
153
|
{ key: "4", label: "Cancel" },
|
|
153
154
|
], "4");
|
|
154
155
|
if (choice === "4")
|
|
@@ -342,14 +343,131 @@ async function setupRemoteFlow(rl, centralDb, localActiveCount) {
|
|
|
342
343
|
return true;
|
|
343
344
|
}
|
|
344
345
|
// ─── Reconfigure helpers ────────────────────────────────────────────────
|
|
345
|
-
async function disconnectRemote(rl,
|
|
346
|
-
const
|
|
346
|
+
async function disconnectRemote(rl, localDb) {
|
|
347
|
+
const remotePath = getConfiguredRemotePath(localDb);
|
|
348
|
+
if (!remotePath) {
|
|
349
|
+
printStatus("warn", "remote is not configured");
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
const localCounts = localDb.getMemoryCount();
|
|
353
|
+
let remoteCounts = null;
|
|
354
|
+
let syncStatus = null;
|
|
355
|
+
const validation = await validateLocation(remotePath);
|
|
356
|
+
const sync = new RemoteSync(localDb, remotePath);
|
|
357
|
+
try {
|
|
358
|
+
syncStatus = await sync.getStatus();
|
|
359
|
+
const remoteDb = new GnosysDB(remotePath);
|
|
360
|
+
if (remoteDb.isAvailable()) {
|
|
361
|
+
remoteCounts = remoteDb.getMemoryCount();
|
|
362
|
+
remoteDb.close();
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
catch {
|
|
366
|
+
// Remote unreadable — still show warning with validation hints below.
|
|
367
|
+
}
|
|
368
|
+
finally {
|
|
369
|
+
sync.closeRemote();
|
|
370
|
+
}
|
|
371
|
+
const remoteReachable = Boolean(syncStatus?.reachable && validation.ok);
|
|
372
|
+
const remoteActive = remoteCounts?.active ?? validation.checks.existingDb.memoryCount ?? null;
|
|
373
|
+
console.log("");
|
|
374
|
+
printStatus("warn", "disconnecting makes this machine local-only");
|
|
375
|
+
console.log(` local ~/.gnosys/gnosys.db — ${localCounts.active} active memories`);
|
|
376
|
+
if (remoteReachable && remoteActive !== null) {
|
|
377
|
+
console.log(` remote ${remotePath} — ${remoteActive} active memories`);
|
|
378
|
+
}
|
|
379
|
+
else {
|
|
380
|
+
printStatus("warn", "remote is not reachable", remotePath);
|
|
381
|
+
}
|
|
382
|
+
console.log("");
|
|
383
|
+
console.log(" The remote folder and gnosys.db are not deleted.");
|
|
384
|
+
console.log(" After disconnect, this Mac will not read or write that remote,");
|
|
385
|
+
console.log(" even when the volume is mounted.");
|
|
386
|
+
const pendingPull = syncStatus?.pendingPull ?? 0;
|
|
387
|
+
const pendingPush = syncStatus?.pendingPush ?? 0;
|
|
388
|
+
const conflicts = syncStatus?.conflicts.length ?? 0;
|
|
389
|
+
const countGap = remoteActive !== null && remoteActive > localCounts.active
|
|
390
|
+
? remoteActive - localCounts.active
|
|
391
|
+
: 0;
|
|
392
|
+
const shouldRecommendSync = remoteReachable &&
|
|
393
|
+
(pendingPull > 0 || pendingPush > 0 || conflicts > 0 || countGap > 0);
|
|
394
|
+
if (shouldRecommendSync) {
|
|
395
|
+
console.log("");
|
|
396
|
+
printStatus("warn", "your local cache may be behind the shared remote brain");
|
|
397
|
+
if (countGap > 0) {
|
|
398
|
+
console.log(` Remote has about ${countGap} more active memor${countGap === 1 ? "y" : "ies"} than local.`);
|
|
399
|
+
}
|
|
400
|
+
if (pendingPull > 0 || pendingPush > 0) {
|
|
401
|
+
const parts = [];
|
|
402
|
+
if (pendingPull > 0)
|
|
403
|
+
parts.push(`${pendingPull} to pull into local`);
|
|
404
|
+
if (pendingPush > 0)
|
|
405
|
+
parts.push(`${pendingPush} to push to remote`);
|
|
406
|
+
console.log(` Pending sync: ${parts.join(", ")}.`);
|
|
407
|
+
}
|
|
408
|
+
if (conflicts > 0) {
|
|
409
|
+
console.log(` ${conflicts} unresolved conflict${conflicts === 1 ? "" : "s"} — resolve before or during sync.`);
|
|
410
|
+
}
|
|
411
|
+
printStatus("progress", "recommended", "gnosys setup remote sync");
|
|
412
|
+
}
|
|
413
|
+
const choice = await askChoice(rl, shouldRecommendSync
|
|
414
|
+
? "Sync first so you do not lose access to remote-only memories on this Mac:"
|
|
415
|
+
: "How do you want to proceed?", remoteReachable
|
|
416
|
+
? [
|
|
417
|
+
{ key: "1", label: "Run gnosys setup remote sync, then disconnect (recommended)" },
|
|
418
|
+
{ key: "2", label: "Disconnect now without syncing (keep current local DB only)" },
|
|
419
|
+
{ key: "3", label: "Cancel" },
|
|
420
|
+
]
|
|
421
|
+
: [
|
|
422
|
+
{ key: "2", label: "Disconnect anyway (local DB only; remote not reachable to sync)" },
|
|
423
|
+
{ key: "3", label: "Cancel" },
|
|
424
|
+
], "3");
|
|
425
|
+
if (choice === "3") {
|
|
426
|
+
console.log("Cancelled.");
|
|
427
|
+
return false;
|
|
428
|
+
}
|
|
429
|
+
if (choice === "1" && remoteReachable) {
|
|
430
|
+
const spin = Spinner("syncing local and remote…");
|
|
431
|
+
const syncRun = new RemoteSync(localDb, remotePath);
|
|
432
|
+
try {
|
|
433
|
+
const result = await syncRun.sync();
|
|
434
|
+
if (result.errors.length > 0) {
|
|
435
|
+
spin.fail("sync had errors");
|
|
436
|
+
for (const e of result.errors)
|
|
437
|
+
printStatus("fail", e);
|
|
438
|
+
const proceed = await askConfirm(rl, "Disconnect anyway without a clean sync?", false);
|
|
439
|
+
if (!proceed)
|
|
440
|
+
return false;
|
|
441
|
+
}
|
|
442
|
+
else {
|
|
443
|
+
spin.ok("sync complete", `pushed ${result.pushed} · pulled ${result.pulled} · conflicts ${result.conflicts.length}`);
|
|
444
|
+
if (result.conflicts.length > 0) {
|
|
445
|
+
printStatus("warn", "conflicts still open", "gnosys setup remote resolve <id> --keep <local|remote>");
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
finally {
|
|
450
|
+
syncRun.closeRemote();
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
else if (choice === "1" && !remoteReachable) {
|
|
454
|
+
printStatus("fail", "cannot sync — mount the remote or cancel");
|
|
455
|
+
return false;
|
|
456
|
+
}
|
|
457
|
+
if (choice === "2" && shouldRecommendSync) {
|
|
458
|
+
const risky = await askConfirm(rl, "Disconnect without syncing? You may only see local memories on this Mac until you reconnect.", false);
|
|
459
|
+
if (!risky) {
|
|
460
|
+
console.log("Cancelled.");
|
|
461
|
+
return false;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
const confirm = await askConfirm(rl, "Disconnect now? Remote files stay on disk; this machine uses ~/.gnosys/gnosys.db only.", false);
|
|
347
465
|
if (!confirm) {
|
|
348
466
|
console.log("Cancelled.");
|
|
349
467
|
return false;
|
|
350
468
|
}
|
|
351
|
-
|
|
352
|
-
console.log("✓ Remote disconnected. Gnosys is now local-only.");
|
|
469
|
+
clearRemoteSyncConfig(localDb);
|
|
470
|
+
console.log("✓ Remote disconnected. Gnosys is now local-only on this machine.");
|
|
353
471
|
return true;
|
|
354
472
|
}
|
|
355
473
|
async function revalidateRemote(_rl, _centralDb, currentRemote) {
|
package/dist/lib/setup.js
CHANGED
|
@@ -18,6 +18,7 @@ import { loadConfig, updateConfig, getProviderModel, } from "./config.js";
|
|
|
18
18
|
import { validateModel } from "./modelValidation.js";
|
|
19
19
|
import { resolveActiveStorePath, ensureActiveStorePath } from "./setup/storePath.js";
|
|
20
20
|
import { safeQuestion } from "./setup/ui/safePrompt.js";
|
|
21
|
+
import { getClaudeDesktopConfigPath, getApiKeySkipHints } from "./platform.js";
|
|
21
22
|
// ─── ANSI Colors ────────────────────────────────────────────────────────────
|
|
22
23
|
const BOLD = "\x1b[1m";
|
|
23
24
|
const DIM = "\x1b[2m";
|
|
@@ -538,7 +539,7 @@ export async function detectIDEs(projectDir) {
|
|
|
538
539
|
// Check for Claude Desktop — distinct from Claude Code CLI. Detected via the
|
|
539
540
|
// app bundle on macOS or the platform-specific config dir.
|
|
540
541
|
try {
|
|
541
|
-
const cfg =
|
|
542
|
+
const cfg = getClaudeDesktopConfigPath();
|
|
542
543
|
const cfgDir = path.dirname(cfg);
|
|
543
544
|
const stat = await fs.stat(cfgDir);
|
|
544
545
|
if (stat.isDirectory())
|
|
@@ -566,23 +567,6 @@ export async function detectIDEs(projectDir) {
|
|
|
566
567
|
}
|
|
567
568
|
return detected;
|
|
568
569
|
}
|
|
569
|
-
/**
|
|
570
|
-
* Resolve the platform-specific Claude Desktop config file path.
|
|
571
|
-
* macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
|
|
572
|
-
* Windows: %APPDATA%/Claude/claude_desktop_config.json
|
|
573
|
-
* Linux: ~/.config/Claude/claude_desktop_config.json (no official build yet)
|
|
574
|
-
*/
|
|
575
|
-
function claudeDesktopConfigPath() {
|
|
576
|
-
const home = os.homedir();
|
|
577
|
-
if (process.platform === "darwin") {
|
|
578
|
-
return path.join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
579
|
-
}
|
|
580
|
-
if (process.platform === "win32") {
|
|
581
|
-
const appData = process.env.APPDATA || path.join(home, "AppData", "Roaming");
|
|
582
|
-
return path.join(appData, "Claude", "claude_desktop_config.json");
|
|
583
|
-
}
|
|
584
|
-
return path.join(home, ".config", "Claude", "claude_desktop_config.json");
|
|
585
|
-
}
|
|
586
570
|
/**
|
|
587
571
|
* Replace (or append) a `[mcp.<name>]` block inside the TOML text for
|
|
588
572
|
* Grok Build's config file. Preserves every line outside that block —
|
|
@@ -634,6 +618,25 @@ export function upsertGrokMcpBlock(existing, name, entry) {
|
|
|
634
618
|
const afterBlock = afterLines.join("\n");
|
|
635
619
|
return `${head}${sectionHeader}\n${blockBody}${gap}${afterBlock}`;
|
|
636
620
|
}
|
|
621
|
+
/**
|
|
622
|
+
* Absolute path to the `gnosys-mcp` stdio entry (dist/index.js).
|
|
623
|
+
* Prefer this over `gnosys serve` — v5.11.0 `gnosys serve` imported index.js but
|
|
624
|
+
* did not call startMcpServer(), so MCP hosts saw "connection closed" on init.
|
|
625
|
+
*/
|
|
626
|
+
function resolveGnosysMcpCommand() {
|
|
627
|
+
try {
|
|
628
|
+
const p = execSync("command -v gnosys-mcp", { encoding: "utf-8" }).trim();
|
|
629
|
+
if (p)
|
|
630
|
+
return p;
|
|
631
|
+
}
|
|
632
|
+
catch {
|
|
633
|
+
// Fall back to bare name on PATH.
|
|
634
|
+
}
|
|
635
|
+
return "gnosys-mcp";
|
|
636
|
+
}
|
|
637
|
+
function gnosysMcpServerEntry() {
|
|
638
|
+
return { command: resolveGnosysMcpCommand(), args: [] };
|
|
639
|
+
}
|
|
637
640
|
function renderGrokMcpBlock(entry) {
|
|
638
641
|
const argsStr = `[${entry.args.map((a) => JSON.stringify(a)).join(", ")}]`;
|
|
639
642
|
const lines = [
|
|
@@ -652,8 +655,15 @@ export async function setupIDE(ide, projectDir) {
|
|
|
652
655
|
try {
|
|
653
656
|
switch (ide) {
|
|
654
657
|
case "claude": {
|
|
658
|
+
const mcpCmd = resolveGnosysMcpCommand();
|
|
655
659
|
try {
|
|
656
|
-
|
|
660
|
+
try {
|
|
661
|
+
execSync("claude mcp remove gnosys", { stdio: "pipe" });
|
|
662
|
+
}
|
|
663
|
+
catch {
|
|
664
|
+
// Not registered yet — fine.
|
|
665
|
+
}
|
|
666
|
+
execSync(`claude mcp add -s user gnosys -- ${mcpCmd}`, {
|
|
657
667
|
stdio: "pipe",
|
|
658
668
|
});
|
|
659
669
|
}
|
|
@@ -664,7 +674,7 @@ export async function setupIDE(ide, projectDir) {
|
|
|
664
674
|
}
|
|
665
675
|
throw e;
|
|
666
676
|
}
|
|
667
|
-
return { success: true, message:
|
|
677
|
+
return { success: true, message: `Claude Code MCP server registered (${mcpCmd})` };
|
|
668
678
|
}
|
|
669
679
|
case "cursor": {
|
|
670
680
|
const cursorDir = path.join(projectDir, ".cursor");
|
|
@@ -680,7 +690,7 @@ export async function setupIDE(ide, projectDir) {
|
|
|
680
690
|
}
|
|
681
691
|
// Merge gnosys entry
|
|
682
692
|
const servers = (config.mcpServers ?? {});
|
|
683
|
-
servers.gnosys =
|
|
693
|
+
servers.gnosys = gnosysMcpServerEntry();
|
|
684
694
|
config.mcpServers = servers;
|
|
685
695
|
await fs.writeFile(mcpPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
686
696
|
return { success: true, message: "Cursor MCP config updated (.cursor/mcp.json)" };
|
|
@@ -713,15 +723,8 @@ export async function setupIDE(ide, projectDir) {
|
|
|
713
723
|
catch {
|
|
714
724
|
// No user-level config.toml to clean — fine.
|
|
715
725
|
}
|
|
716
|
-
// 2.
|
|
717
|
-
|
|
718
|
-
let gnosysCmd = "gnosys";
|
|
719
|
-
try {
|
|
720
|
-
gnosysCmd = execSync("command -v gnosys", { encoding: "utf-8" }).trim() || "gnosys";
|
|
721
|
-
}
|
|
722
|
-
catch {
|
|
723
|
-
// Fall back to `gnosys` on PATH if `command -v` fails.
|
|
724
|
-
}
|
|
726
|
+
// 2. Absolute path to `gnosys-mcp` (stdio entry) for Codex spawn.
|
|
727
|
+
const gnosysCmd = resolveGnosysMcpCommand();
|
|
725
728
|
// 3. Check whether gnosys is already registered. If yes and the
|
|
726
729
|
// command matches, leave it alone (idempotent). If it differs,
|
|
727
730
|
// remove and re-add.
|
|
@@ -730,7 +733,7 @@ export async function setupIDE(ide, projectDir) {
|
|
|
730
733
|
const existing = execSync("codex mcp get gnosys 2>/dev/null", {
|
|
731
734
|
encoding: "utf-8",
|
|
732
735
|
});
|
|
733
|
-
if (existing && existing.includes(gnosysCmd) && existing.includes("serve")) {
|
|
736
|
+
if (existing && existing.includes(gnosysCmd) && !existing.includes(" serve")) {
|
|
734
737
|
alreadyCorrect = true;
|
|
735
738
|
}
|
|
736
739
|
else if (existing) {
|
|
@@ -755,7 +758,7 @@ export async function setupIDE(ide, projectDir) {
|
|
|
755
758
|
}
|
|
756
759
|
// 4. Register via the canonical Codex CLI command.
|
|
757
760
|
try {
|
|
758
|
-
execSync(`codex mcp add gnosys -- ${gnosysCmd}
|
|
761
|
+
execSync(`codex mcp add gnosys -- ${gnosysCmd}`, { stdio: "pipe" });
|
|
759
762
|
}
|
|
760
763
|
catch (err) {
|
|
761
764
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -785,7 +788,7 @@ export async function setupIDE(ide, projectDir) {
|
|
|
785
788
|
// File doesn't exist or is invalid — start fresh
|
|
786
789
|
}
|
|
787
790
|
const servers = (config.mcpServers ?? {});
|
|
788
|
-
servers.gnosys =
|
|
791
|
+
servers.gnosys = gnosysMcpServerEntry();
|
|
789
792
|
config.mcpServers = servers;
|
|
790
793
|
await fs.writeFile(settingsPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
791
794
|
return { success: true, message: "Gemini CLI MCP config updated (~/.gemini/settings.json)" };
|
|
@@ -805,7 +808,7 @@ export async function setupIDE(ide, projectDir) {
|
|
|
805
808
|
// File doesn't exist or is invalid — start fresh
|
|
806
809
|
}
|
|
807
810
|
const servers = (config.mcpServers ?? {});
|
|
808
|
-
servers.gnosys =
|
|
811
|
+
servers.gnosys = gnosysMcpServerEntry();
|
|
809
812
|
config.mcpServers = servers;
|
|
810
813
|
await fs.writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
811
814
|
return { success: true, message: "Antigravity MCP config updated (~/.gemini/antigravity/mcp_config.json)" };
|
|
@@ -826,8 +829,7 @@ export async function setupIDE(ide, projectDir) {
|
|
|
826
829
|
// File doesn't exist yet — start fresh
|
|
827
830
|
}
|
|
828
831
|
const updated = upsertGrokMcpBlock(existing, "gnosys", {
|
|
829
|
-
|
|
830
|
-
args: ["serve"],
|
|
832
|
+
...gnosysMcpServerEntry(),
|
|
831
833
|
startup_timeout_sec: 90,
|
|
832
834
|
});
|
|
833
835
|
await fs.writeFile(configPath, updated, "utf-8");
|
|
@@ -837,7 +839,7 @@ export async function setupIDE(ide, projectDir) {
|
|
|
837
839
|
// Claude Desktop reads MCP servers from claude_desktop_config.json
|
|
838
840
|
// in a platform-specific app data directory. Distinct from Claude
|
|
839
841
|
// Code CLI which uses `claude mcp add`.
|
|
840
|
-
const configPath =
|
|
842
|
+
const configPath = getClaudeDesktopConfigPath();
|
|
841
843
|
const configDir = path.dirname(configPath);
|
|
842
844
|
await fs.mkdir(configDir, { recursive: true });
|
|
843
845
|
let config = {};
|
|
@@ -849,7 +851,7 @@ export async function setupIDE(ide, projectDir) {
|
|
|
849
851
|
// File doesn't exist or is invalid — start fresh
|
|
850
852
|
}
|
|
851
853
|
const servers = (config.mcpServers ?? {});
|
|
852
|
-
servers.gnosys =
|
|
854
|
+
servers.gnosys = gnosysMcpServerEntry();
|
|
853
855
|
config.mcpServers = servers;
|
|
854
856
|
await fs.writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
855
857
|
// Display path with ~ prefix when inside HOME for clarity
|
|
@@ -1432,14 +1434,10 @@ export async function runSetup(opts) {
|
|
|
1432
1434
|
else {
|
|
1433
1435
|
// Skip
|
|
1434
1436
|
console.log(` ${DIM}Skipped. Set your key later using one of these methods:`);
|
|
1435
|
-
|
|
1436
|
-
console.log(` \u2022
|
|
1437
|
-
}
|
|
1438
|
-
if (hasSecret) {
|
|
1439
|
-
console.log(` \u2022 GNOME Keyring: printf '%s' 'key' | secret-tool store --label="Gnosys ${provider}" service gnosys account ${envVarName}`);
|
|
1437
|
+
for (const hint of getApiKeySkipHints(envVarName, provider)) {
|
|
1438
|
+
console.log(` \u2022 ${hint}`);
|
|
1440
1439
|
}
|
|
1441
|
-
console.log(
|
|
1442
|
-
console.log(` \u2022 Dotenv file: echo '${envVarName}=key' >> ~/.config/gnosys/.env${RESET}`);
|
|
1440
|
+
console.log(`${RESET}`);
|
|
1443
1441
|
}
|
|
1444
1442
|
}
|
|
1445
1443
|
}
|
package/dist/lib/upgrade.d.ts
CHANGED
|
@@ -30,8 +30,11 @@ export declare function getMarkerPath(): string;
|
|
|
30
30
|
export declare function writeUpgradeMarker(version: string): void;
|
|
31
31
|
export declare function readUpgradeMarker(): UpgradeMarker | null;
|
|
32
32
|
/**
|
|
33
|
-
* Returns true when the on-disk marker names a
|
|
33
|
+
* Returns true when the on-disk marker names a **newer** version than the
|
|
34
34
|
* currently running binary. The caller (an MCP server) should exit cleanly
|
|
35
35
|
* so the host respawns it against the upgraded global binary.
|
|
36
|
+
*
|
|
37
|
+
* Stale markers from an older install must not restart a newer binary (that
|
|
38
|
+
* caused an immediate exit loop after patch upgrades).
|
|
36
39
|
*/
|
|
37
40
|
export declare function shouldRestartMcp(currentVersion: string): boolean;
|
package/dist/lib/upgrade.js
CHANGED
|
@@ -47,14 +47,27 @@ export function readUpgradeMarker() {
|
|
|
47
47
|
return null;
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
|
+
function compareSemver(a, b) {
|
|
51
|
+
const pa = a.split(".").map((x) => parseInt(x, 10) || 0);
|
|
52
|
+
const pb = b.split(".").map((x) => parseInt(x, 10) || 0);
|
|
53
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
54
|
+
const d = (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
55
|
+
if (d !== 0)
|
|
56
|
+
return d;
|
|
57
|
+
}
|
|
58
|
+
return 0;
|
|
59
|
+
}
|
|
50
60
|
/**
|
|
51
|
-
* Returns true when the on-disk marker names a
|
|
61
|
+
* Returns true when the on-disk marker names a **newer** version than the
|
|
52
62
|
* currently running binary. The caller (an MCP server) should exit cleanly
|
|
53
63
|
* so the host respawns it against the upgraded global binary.
|
|
64
|
+
*
|
|
65
|
+
* Stale markers from an older install must not restart a newer binary (that
|
|
66
|
+
* caused an immediate exit loop after patch upgrades).
|
|
54
67
|
*/
|
|
55
68
|
export function shouldRestartMcp(currentVersion) {
|
|
56
69
|
const marker = readUpgradeMarker();
|
|
57
70
|
if (!marker)
|
|
58
71
|
return false;
|
|
59
|
-
return marker.version
|
|
72
|
+
return compareSemver(marker.version, currentVersion) > 0;
|
|
60
73
|
}
|
package/dist/lib/videoExtract.js
CHANGED
|
@@ -11,6 +11,7 @@ import * as os from "os";
|
|
|
11
11
|
import * as fs from "fs/promises";
|
|
12
12
|
import { execFileSync } from "child_process";
|
|
13
13
|
import { transcribeAudio, } from "./audioExtract.js";
|
|
14
|
+
import { formatFfmpegInstallHint } from "./platform.js";
|
|
14
15
|
// ─── Helpers ────────────────────────────────────────────────────────────
|
|
15
16
|
/**
|
|
16
17
|
* Check that ffmpeg is installed and accessible.
|
|
@@ -22,10 +23,7 @@ function checkFfmpeg() {
|
|
|
22
23
|
}
|
|
23
24
|
catch {
|
|
24
25
|
throw new Error("Video transcription requires ffmpeg to be installed.\n" +
|
|
25
|
-
|
|
26
|
-
" macOS: brew install ffmpeg\n" +
|
|
27
|
-
" Ubuntu: sudo apt install ffmpeg\n" +
|
|
28
|
-
" Windows: winget install FFmpeg");
|
|
26
|
+
formatFfmpegInstallHint());
|
|
29
27
|
}
|
|
30
28
|
}
|
|
31
29
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gnosys",
|
|
3
|
-
"version": "5.11.
|
|
3
|
+
"version": "5.11.1",
|
|
4
4
|
"description": "Gnosys — Persistent Memory for AI Agents. Sandbox-first runtime, central SQLite brain, federated search, Dream Mode, Web Knowledge Base, Obsidian export.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|