gnosys 5.11.4 → 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.
Files changed (265) hide show
  1. package/dist/cli.js +377 -5162
  2. package/dist/index.js +542 -244
  3. package/dist/lib/addCommand.d.ts +9 -0
  4. package/dist/lib/addCommand.js +102 -0
  5. package/dist/lib/addStructuredCommand.d.ts +16 -0
  6. package/dist/lib/addStructuredCommand.js +103 -0
  7. package/dist/lib/ambiguityCommand.d.ts +4 -0
  8. package/dist/lib/ambiguityCommand.js +36 -0
  9. package/dist/lib/apiKeyVault.d.ts +78 -0
  10. package/dist/lib/apiKeyVault.js +447 -0
  11. package/dist/lib/archive.js +0 -2
  12. package/dist/lib/askCommand.d.ts +13 -0
  13. package/dist/lib/askCommand.js +145 -0
  14. package/dist/lib/attachCommand.d.ts +17 -0
  15. package/dist/lib/attachCommand.js +66 -0
  16. package/dist/lib/attachments.d.ts +43 -2
  17. package/dist/lib/attachments.js +81 -2
  18. package/dist/lib/audioExtract.js +4 -1
  19. package/dist/lib/auditCommand.d.ts +7 -0
  20. package/dist/lib/auditCommand.js +27 -0
  21. package/dist/lib/backupCommand.d.ts +6 -0
  22. package/dist/lib/backupCommand.js +54 -0
  23. package/dist/lib/bootstrapCommand.d.ts +15 -0
  24. package/dist/lib/bootstrapCommand.js +51 -0
  25. package/dist/lib/briefingCommand.d.ts +7 -0
  26. package/dist/lib/briefingCommand.js +92 -0
  27. package/dist/lib/centralizeCommand.d.ts +5 -0
  28. package/dist/lib/centralizeCommand.js +16 -0
  29. package/dist/lib/chat/choose.js +2 -2
  30. package/dist/lib/chatCommand.d.ts +12 -0
  31. package/dist/lib/chatCommand.js +46 -0
  32. package/dist/lib/checkCommand.d.ts +4 -0
  33. package/dist/lib/checkCommand.js +133 -0
  34. package/dist/lib/clientReadOverlay.d.ts +27 -0
  35. package/dist/lib/clientReadOverlay.js +76 -0
  36. package/dist/lib/clientReadResolve.d.ts +32 -0
  37. package/dist/lib/clientReadResolve.js +84 -0
  38. package/dist/lib/commitContextCommand.d.ts +9 -0
  39. package/dist/lib/commitContextCommand.js +142 -0
  40. package/dist/lib/config.d.ts +41 -48
  41. package/dist/lib/config.js +58 -57
  42. package/dist/lib/configCommand.d.ts +10 -0
  43. package/dist/lib/configCommand.js +321 -0
  44. package/dist/lib/connectCommand.d.ts +8 -0
  45. package/dist/lib/connectCommand.js +19 -0
  46. package/dist/lib/db.d.ts +68 -1
  47. package/dist/lib/db.js +385 -120
  48. package/dist/lib/dbWrite.d.ts +1 -1
  49. package/dist/lib/dearchiveCommand.d.ts +7 -0
  50. package/dist/lib/dearchiveCommand.js +41 -0
  51. package/dist/lib/discoverCommand.d.ts +9 -0
  52. package/dist/lib/discoverCommand.js +87 -0
  53. package/dist/lib/doctorCommand.d.ts +6 -0
  54. package/dist/lib/doctorCommand.js +256 -0
  55. package/dist/lib/docxExtract.js +1 -1
  56. package/dist/lib/dream.d.ts +50 -2
  57. package/dist/lib/dream.js +324 -30
  58. package/dist/lib/dreamCommand.d.ts +10 -0
  59. package/dist/lib/dreamCommand.js +195 -0
  60. package/dist/lib/dreamLaunchd.d.ts +2 -0
  61. package/dist/lib/dreamLaunchd.js +72 -0
  62. package/dist/lib/dreamLogCommand.d.ts +10 -0
  63. package/dist/lib/dreamLogCommand.js +58 -0
  64. package/dist/lib/dreamReport.d.ts +7 -0
  65. package/dist/lib/dreamReport.js +114 -0
  66. package/dist/lib/dreamRunLog.d.ts +121 -0
  67. package/dist/lib/dreamRunLog.js +234 -0
  68. package/dist/lib/embeddings.js +3 -3
  69. package/dist/lib/exportCommand.d.ts +18 -0
  70. package/dist/lib/exportCommand.js +101 -0
  71. package/dist/lib/exportProject.d.ts +3 -2
  72. package/dist/lib/exportProject.js +2 -1
  73. package/dist/lib/federated.js +1 -1
  74. package/dist/lib/fsearchCommand.d.ts +8 -0
  75. package/dist/lib/fsearchCommand.js +44 -0
  76. package/dist/lib/graphCommand.d.ts +4 -0
  77. package/dist/lib/graphCommand.js +68 -0
  78. package/dist/lib/helperGenerateCommand.d.ts +5 -0
  79. package/dist/lib/helperGenerateCommand.js +27 -0
  80. package/dist/lib/historyCommand.d.ts +5 -0
  81. package/dist/lib/historyCommand.js +51 -0
  82. package/dist/lib/hybridSearchCommand.d.ts +12 -0
  83. package/dist/lib/hybridSearchCommand.js +95 -0
  84. package/dist/lib/importCommand.d.ts +16 -0
  85. package/dist/lib/importCommand.js +89 -0
  86. package/dist/lib/importProject.js +2 -1
  87. package/dist/lib/importProjectCommand.d.ts +6 -0
  88. package/dist/lib/importProjectCommand.js +43 -0
  89. package/dist/lib/ingestCommand.d.ts +13 -0
  90. package/dist/lib/ingestCommand.js +95 -0
  91. package/dist/lib/installOutput.d.ts +36 -0
  92. package/dist/lib/installOutput.js +55 -0
  93. package/dist/lib/lensCommand.d.ts +20 -0
  94. package/dist/lib/lensCommand.js +61 -0
  95. package/dist/lib/lensing.d.ts +1 -0
  96. package/dist/lib/lensing.js +50 -9
  97. package/dist/lib/linksCommand.d.ts +7 -0
  98. package/dist/lib/linksCommand.js +48 -0
  99. package/dist/lib/listCommand.d.ts +8 -0
  100. package/dist/lib/listCommand.js +74 -0
  101. package/dist/lib/llm.d.ts +1 -1
  102. package/dist/lib/llm.js +27 -9
  103. package/dist/lib/localDiskCheck.d.ts +17 -0
  104. package/dist/lib/localDiskCheck.js +54 -0
  105. package/dist/lib/lock.d.ts +1 -1
  106. package/dist/lib/lock.js +5 -3
  107. package/dist/lib/machineConfig.d.ts +11 -1
  108. package/dist/lib/machineConfig.js +16 -0
  109. package/dist/lib/machineRegistry.d.ts +61 -0
  110. package/dist/lib/machineRegistry.js +80 -0
  111. package/dist/lib/maintainCommand.d.ts +8 -0
  112. package/dist/lib/maintainCommand.js +34 -0
  113. package/dist/lib/masterLease.d.ts +20 -0
  114. package/dist/lib/masterLease.js +68 -0
  115. package/dist/lib/migrate.js +0 -1
  116. package/dist/lib/migrateCommand.d.ts +7 -0
  117. package/dist/lib/migrateCommand.js +158 -0
  118. package/dist/lib/migrateDbCommand.d.ts +9 -0
  119. package/dist/lib/migrateDbCommand.js +94 -0
  120. package/dist/lib/modelValidation.d.ts +5 -0
  121. package/dist/lib/modelValidation.js +27 -0
  122. package/dist/lib/multimodalIngest.js +1 -1
  123. package/dist/lib/openrouterTiers.d.ts +29 -0
  124. package/dist/lib/openrouterTiers.js +113 -0
  125. package/dist/lib/platform.d.ts +0 -6
  126. package/dist/lib/platform.js +0 -28
  127. package/dist/lib/prefCommand.d.ts +10 -0
  128. package/dist/lib/prefCommand.js +118 -0
  129. package/dist/lib/projectsCommand.d.ts +8 -0
  130. package/dist/lib/projectsCommand.js +131 -0
  131. package/dist/lib/readCommand.d.ts +7 -0
  132. package/dist/lib/readCommand.js +63 -0
  133. package/dist/lib/recall.d.ts +3 -0
  134. package/dist/lib/recall.js +19 -4
  135. package/dist/lib/recallCommand.d.ts +11 -0
  136. package/dist/lib/recallCommand.js +112 -0
  137. package/dist/lib/reflectCommand.d.ts +8 -0
  138. package/dist/lib/reflectCommand.js +61 -0
  139. package/dist/lib/reindexCommand.d.ts +4 -0
  140. package/dist/lib/reindexCommand.js +34 -0
  141. package/dist/lib/reindexGraphCommand.d.ts +4 -0
  142. package/dist/lib/reindexGraphCommand.js +12 -0
  143. package/dist/lib/reinforceCommand.d.ts +8 -0
  144. package/dist/lib/reinforceCommand.js +40 -0
  145. package/dist/lib/remote.d.ts +5 -1
  146. package/dist/lib/remote.js +5 -1
  147. package/dist/lib/remoteWizard.d.ts +24 -5
  148. package/dist/lib/remoteWizard.js +308 -319
  149. package/dist/lib/restoreCommand.d.ts +5 -0
  150. package/dist/lib/restoreCommand.js +35 -0
  151. package/dist/lib/rulesGen.d.ts +8 -0
  152. package/dist/lib/rulesGen.js +16 -0
  153. package/dist/lib/sandboxStartCommand.d.ts +6 -0
  154. package/dist/lib/sandboxStartCommand.js +25 -0
  155. package/dist/lib/sandboxStatusCommand.d.ts +4 -0
  156. package/dist/lib/sandboxStatusCommand.js +24 -0
  157. package/dist/lib/sandboxStopCommand.d.ts +4 -0
  158. package/dist/lib/sandboxStopCommand.js +21 -0
  159. package/dist/lib/search.d.ts +0 -2
  160. package/dist/lib/search.js +0 -7
  161. package/dist/lib/searchCommand.d.ts +9 -0
  162. package/dist/lib/searchCommand.js +90 -0
  163. package/dist/lib/semanticSearchCommand.d.ts +8 -0
  164. package/dist/lib/semanticSearchCommand.js +52 -0
  165. package/dist/lib/setup/configSetRender.js +2 -0
  166. package/dist/lib/setup/providerGlyphs.d.ts +19 -0
  167. package/dist/lib/setup/providerGlyphs.js +42 -0
  168. package/dist/lib/setup/remoteRender.d.ts +31 -1
  169. package/dist/lib/setup/remoteRender.js +95 -4
  170. package/dist/lib/setup/sections/providers.d.ts +17 -0
  171. package/dist/lib/setup/sections/providers.js +307 -0
  172. package/dist/lib/setup/sections/routing.d.ts +2 -6
  173. package/dist/lib/setup/sections/routing.js +67 -82
  174. package/dist/lib/setup/sections/taskRoutingEditor.d.ts +13 -0
  175. package/dist/lib/setup/sections/taskRoutingEditor.js +139 -0
  176. package/dist/lib/setup/summary.d.ts +9 -0
  177. package/dist/lib/setup/summary.js +51 -37
  178. package/dist/lib/setup/ui/header.js +0 -1
  179. package/dist/lib/setup.d.ts +105 -15
  180. package/dist/lib/setup.js +747 -287
  181. package/dist/lib/setupKeys.d.ts +42 -0
  182. package/dist/lib/setupKeys.js +564 -0
  183. package/dist/lib/setupRemoteCommand.d.ts +4 -0
  184. package/dist/lib/setupRemoteCommand.js +28 -0
  185. package/dist/lib/setupRemotePullCommand.d.ts +5 -0
  186. package/dist/lib/setupRemotePullCommand.js +52 -0
  187. package/dist/lib/setupRemotePushCommand.d.ts +5 -0
  188. package/dist/lib/setupRemotePushCommand.js +57 -0
  189. package/dist/lib/setupRemoteResolveCommand.d.ts +4 -0
  190. package/dist/lib/setupRemoteResolveCommand.js +48 -0
  191. package/dist/lib/setupRemoteStatusCommand.d.ts +4 -0
  192. package/dist/lib/setupRemoteStatusCommand.js +73 -0
  193. package/dist/lib/setupRemoteSyncCommand.d.ts +6 -0
  194. package/dist/lib/setupRemoteSyncCommand.js +65 -0
  195. package/dist/lib/setupSyncProjectsCommand.d.ts +4 -0
  196. package/dist/lib/setupSyncProjectsCommand.js +292 -0
  197. package/dist/lib/staleCommand.d.ts +8 -0
  198. package/dist/lib/staleCommand.js +34 -0
  199. package/dist/lib/statsCommand.d.ts +6 -0
  200. package/dist/lib/statsCommand.js +142 -0
  201. package/dist/lib/statusCommand.d.ts +18 -0
  202. package/dist/lib/statusCommand.js +250 -0
  203. package/dist/lib/storesCommand.d.ts +2 -0
  204. package/dist/lib/storesCommand.js +4 -0
  205. package/dist/lib/syncClient.d.ts +41 -0
  206. package/dist/lib/syncClient.js +234 -0
  207. package/dist/lib/syncCommand.d.ts +6 -0
  208. package/dist/lib/syncCommand.js +57 -0
  209. package/dist/lib/syncDoctorCommand.d.ts +5 -0
  210. package/dist/lib/syncDoctorCommand.js +100 -0
  211. package/dist/lib/syncIngest.d.ts +30 -0
  212. package/dist/lib/syncIngest.js +175 -0
  213. package/dist/lib/syncIngestLaunchd.d.ts +8 -0
  214. package/dist/lib/syncIngestLaunchd.js +93 -0
  215. package/dist/lib/syncIngestStartup.d.ts +5 -0
  216. package/dist/lib/syncIngestStartup.js +29 -0
  217. package/dist/lib/syncIngestSystemd.d.ts +10 -0
  218. package/dist/lib/syncIngestSystemd.js +97 -0
  219. package/dist/lib/syncIngestTimer.d.ts +8 -0
  220. package/dist/lib/syncIngestTimer.js +27 -0
  221. package/dist/lib/syncIngestTimerCommand.d.ts +7 -0
  222. package/dist/lib/syncIngestTimerCommand.js +83 -0
  223. package/dist/lib/syncLock.d.ts +6 -0
  224. package/dist/lib/syncLock.js +74 -0
  225. package/dist/lib/syncSnapshot.d.ts +32 -0
  226. package/dist/lib/syncSnapshot.js +188 -0
  227. package/dist/lib/syncStaging.d.ts +79 -0
  228. package/dist/lib/syncStaging.js +237 -0
  229. package/dist/lib/tagsAddCommand.d.ts +8 -0
  230. package/dist/lib/tagsAddCommand.js +18 -0
  231. package/dist/lib/tagsCommand.d.ts +4 -0
  232. package/dist/lib/tagsCommand.js +16 -0
  233. package/dist/lib/timelineCommand.d.ts +7 -0
  234. package/dist/lib/timelineCommand.js +49 -0
  235. package/dist/lib/traceCommand.d.ts +6 -0
  236. package/dist/lib/traceCommand.js +39 -0
  237. package/dist/lib/traverseCommand.d.ts +6 -0
  238. package/dist/lib/traverseCommand.js +58 -0
  239. package/dist/lib/updateCommand.d.ts +13 -0
  240. package/dist/lib/updateCommand.js +67 -0
  241. package/dist/lib/updateStatusCommand.d.ts +5 -0
  242. package/dist/lib/updateStatusCommand.js +38 -0
  243. package/dist/lib/webAddCommand.d.ts +8 -0
  244. package/dist/lib/webAddCommand.js +55 -0
  245. package/dist/lib/webBuildCommand.d.ts +10 -0
  246. package/dist/lib/webBuildCommand.js +65 -0
  247. package/dist/lib/webBuildIndexCommand.d.ts +8 -0
  248. package/dist/lib/webBuildIndexCommand.js +37 -0
  249. package/dist/lib/webIndex.js +0 -1
  250. package/dist/lib/webIngestCommand.d.ts +11 -0
  251. package/dist/lib/webIngestCommand.js +51 -0
  252. package/dist/lib/webInitCommand.d.ts +9 -0
  253. package/dist/lib/webInitCommand.js +167 -0
  254. package/dist/lib/webRemoveCommand.d.ts +5 -0
  255. package/dist/lib/webRemoveCommand.js +41 -0
  256. package/dist/lib/webStatusCommand.d.ts +5 -0
  257. package/dist/lib/webStatusCommand.js +94 -0
  258. package/dist/lib/webUpdateCommand.d.ts +7 -0
  259. package/dist/lib/webUpdateCommand.js +72 -0
  260. package/dist/lib/workingSetCommand.d.ts +6 -0
  261. package/dist/lib/workingSetCommand.js +37 -0
  262. package/dist/sandbox/client.js +1 -1
  263. package/dist/sandbox/manager.js +1 -14
  264. package/dist/sandbox/server.js +3 -5
  265. package/package.json +6 -2
@@ -0,0 +1,48 @@
1
+ import { getBacklinks, getOutgoingLinks } from "./wikilinks.js";
2
+ function outputResult(json, data, humanFn) {
3
+ if (json) {
4
+ console.log(JSON.stringify(data, null, 2));
5
+ }
6
+ else {
7
+ humanFn();
8
+ }
9
+ }
10
+ export async function runLinksCommand(getResolver, memoryPath, opts) {
11
+ const resolver = await getResolver();
12
+ const memory = await resolver.readMemory(memoryPath);
13
+ if (!memory) {
14
+ console.error(`Memory not found: ${memoryPath}`);
15
+ process.exit(1);
16
+ }
17
+ const allMemories = await resolver.getAllMemories();
18
+ const outgoing = getOutgoingLinks(allMemories, memory.relativePath);
19
+ const backlinks = getBacklinks(allMemories, memory.relativePath);
20
+ outputResult(!!opts.json, {
21
+ memoryPath,
22
+ title: memory.frontmatter.title,
23
+ outgoing,
24
+ backlinks,
25
+ }, () => {
26
+ console.log(`Links for ${memory.frontmatter.title}:\n`);
27
+ if (outgoing.length > 0) {
28
+ console.log(` Outgoing (${outgoing.length}):`);
29
+ for (const link of outgoing) {
30
+ const display = link.displayText ? ` (${link.displayText})` : "";
31
+ console.log(` → [[${link.target}]]${display}`);
32
+ }
33
+ }
34
+ else {
35
+ console.log(" No outgoing links.");
36
+ }
37
+ console.log();
38
+ if (backlinks.length > 0) {
39
+ console.log(` Backlinks (${backlinks.length}):`);
40
+ for (const link of backlinks) {
41
+ console.log(` ← ${link.sourceTitle} (${link.sourcePath})`);
42
+ }
43
+ }
44
+ else {
45
+ console.log(" No backlinks.");
46
+ }
47
+ });
48
+ }
@@ -0,0 +1,8 @@
1
+ export type ListCommandOptions = {
2
+ category?: string;
3
+ tag?: string;
4
+ store?: string;
5
+ json?: boolean;
6
+ idFormat?: string;
7
+ };
8
+ export declare function runListCommand(opts: ListCommandOptions): Promise<void>;
@@ -0,0 +1,74 @@
1
+ import { logError } from "./log.js";
2
+ import { findProjectIdentity } from "./projectIdentity.js";
3
+ function outputResult(json, data, humanFn) {
4
+ if (json) {
5
+ console.log(JSON.stringify(data, null, 2));
6
+ }
7
+ else {
8
+ humanFn();
9
+ }
10
+ }
11
+ export async function runListCommand(opts) {
12
+ const { resolveClientRead, listMemoriesWithOverlay } = await import("./clientReadResolve.js");
13
+ const resolved = resolveClientRead();
14
+ if (!resolved) {
15
+ console.error("Central DB not available. Run 'gnosys init' first.");
16
+ process.exit(1);
17
+ }
18
+ try {
19
+ const projIdentity = await findProjectIdentity(process.cwd());
20
+ const projectId = projIdentity?.identity.projectId || null;
21
+ let memories = listMemoriesWithOverlay(resolved, (db) => db.getActiveMemories());
22
+ if (projectId) {
23
+ memories = memories.filter((m) => m.project_id === projectId || m.scope === "user" || m.scope === "global");
24
+ }
25
+ if (opts.store) {
26
+ memories = memories.filter((m) => m.scope === opts.store);
27
+ }
28
+ if (opts.category) {
29
+ memories = memories.filter((m) => m.category === opts.category);
30
+ }
31
+ if (opts.tag) {
32
+ memories = memories.filter((m) => {
33
+ try {
34
+ const tags = JSON.parse(m.tags || "[]");
35
+ return tags.includes(opts.tag);
36
+ }
37
+ catch {
38
+ return false;
39
+ }
40
+ });
41
+ }
42
+ const { formatMemoryIdHyperlink: formatMemoryId, buildProjectNameLookup, parseIdFormat } = await import("./idFormat.js");
43
+ const idFormat = parseIdFormat(opts.idFormat);
44
+ const projectNames = buildProjectNameLookup(resolved.localDb);
45
+ outputResult(!!opts.json, {
46
+ count: memories.length,
47
+ memories: memories.map((m) => ({
48
+ id: m.id,
49
+ title: m.title,
50
+ category: m.category,
51
+ status: m.status,
52
+ scope: m.scope,
53
+ confidence: m.confidence,
54
+ project: m.project_id ? projectNames.get(m.project_id) || null : null,
55
+ })),
56
+ }, () => {
57
+ console.log(`${memories.length} memories:\n`);
58
+ for (const m of memories) {
59
+ const projectName = m.project_id ? projectNames.get(m.project_id) || null : null;
60
+ const displayId = formatMemoryId(m.id, projectName, idFormat);
61
+ console.log(` [${m.scope}] [${m.status}] ${m.title}`);
62
+ console.log(` id: ${displayId} | category: ${m.category} | confidence: ${m.confidence}`);
63
+ console.log();
64
+ }
65
+ });
66
+ }
67
+ catch (err) {
68
+ logError(err, { module: "cli", op: "list" });
69
+ process.exit(1);
70
+ }
71
+ finally {
72
+ resolved.release();
73
+ }
74
+ }
package/dist/lib/llm.d.ts CHANGED
@@ -45,7 +45,7 @@ export declare function getLLMProvider(config: GnosysConfig, task?: "structuring
45
45
  /**
46
46
  * Create a specific LLM provider instance.
47
47
  */
48
- export declare function createProvider(provider: LLMProviderName, model: string, config: GnosysConfig): LLMProvider;
48
+ export declare function createProvider(provider: LLMProviderName, model: string, config: GnosysConfig, task?: "structuring" | "synthesis" | "vision" | "transcription" | "chat" | "dream"): LLMProvider;
49
49
  /**
50
50
  * Check if an LLM provider is available (has credentials / connectivity).
51
51
  * Returns { available: boolean; error?: string }.
package/dist/lib/llm.js CHANGED
@@ -3,7 +3,7 @@
3
3
  * Supports Anthropic (cloud) and Ollama (local). Clean factory pattern enables
4
4
  * future providers (Groq, OpenAI, LM Studio) with zero changes to call sites.
5
5
  */
6
- import { DEFAULT_CONFIG, resolveTaskModel, getAnthropicApiKey, getOllamaBaseUrl, getGroqApiKey, getOpenAIApiKey, getOpenAIBaseUrl, getLMStudioBaseUrl, getXAIApiKey, getMistralApiKey, getCustomApiKey, getProviderModel, ALL_PROVIDERS, } from "./config.js";
6
+ import { DEFAULT_CONFIG, resolveTaskModel, getAnthropicApiKey, getOllamaBaseUrl, getGroqApiKey, getOpenAIApiKey, getOpenAIBaseUrl, getLMStudioBaseUrl, getXAIApiKey, getMistralApiKey, getOpenRouterApiKey, getOpenRouterBaseUrl, getCustomApiKey, getProviderModel, ALL_PROVIDERS, } from "./config.js";
7
7
  import { withRetry, isTransientError } from "./retry.js";
8
8
  /** Per-request timeout for LLM generation calls (ms). */
9
9
  const LLM_TIMEOUT_MS = 60_000;
@@ -426,17 +426,17 @@ export function getLLMProvider(config, task) {
426
426
  const resolved = task
427
427
  ? resolveTaskModel(config, task)
428
428
  : { provider: config.llm.defaultProvider, model: getDefaultModel(config) };
429
- return createProvider(resolved.provider, resolved.model, config);
429
+ return createProvider(resolved.provider, resolved.model, config, task);
430
430
  }
431
431
  /**
432
432
  * Create a specific LLM provider instance.
433
433
  */
434
- export function createProvider(provider, model, config) {
434
+ export function createProvider(provider, model, config, task) {
435
435
  switch (provider) {
436
436
  case "anthropic": {
437
437
  const apiKey = getAnthropicApiKey(config);
438
438
  if (!apiKey) {
439
- throw new Error("No Anthropic API key found. Set ANTHROPIC_API_KEY environment variable or add llm.anthropic.apiKey to gnosys.json.");
439
+ throw new Error("No Anthropic API key found. Set GNOSYS_GLOBAL_ANTHROPIC_KEY, GNOSYS_ANTHROPIC_KEY, or ANTHROPIC_API_KEY.");
440
440
  }
441
441
  return new AnthropicProvider(model, apiKey, config);
442
442
  }
@@ -447,14 +447,14 @@ export function createProvider(provider, model, config) {
447
447
  case "groq": {
448
448
  const apiKey = getGroqApiKey(config);
449
449
  if (!apiKey) {
450
- throw new Error("No Groq API key found. Set GROQ_API_KEY environment variable or add llm.groq.apiKey to gnosys.json.");
450
+ throw new Error("No Groq API key found. Set GNOSYS_GLOBAL_GROQ_KEY, GNOSYS_GROQ_KEY, or GROQ_API_KEY.");
451
451
  }
452
452
  return new OpenAICompatibleProvider("groq", model, "https://api.groq.com/openai/v1", apiKey, config);
453
453
  }
454
454
  case "openai": {
455
455
  const apiKey = getOpenAIApiKey(config);
456
456
  if (!apiKey) {
457
- throw new Error("No OpenAI API key found. Set OPENAI_API_KEY environment variable or add llm.openai.apiKey to gnosys.json.");
457
+ throw new Error("No OpenAI API key found. Set GNOSYS_GLOBAL_OPENAI_KEY, GNOSYS_OPENAI_KEY, or OPENAI_API_KEY.");
458
458
  }
459
459
  const baseUrl = getOpenAIBaseUrl(config);
460
460
  return new OpenAICompatibleProvider("openai", model, baseUrl, apiKey, config);
@@ -466,20 +466,28 @@ export function createProvider(provider, model, config) {
466
466
  case "xai": {
467
467
  const apiKey = getXAIApiKey(config);
468
468
  if (!apiKey) {
469
- throw new Error("No xAI API key found. Set XAI_API_KEY environment variable or add llm.xai.apiKey to gnosys.json.");
469
+ throw new Error("No xAI API key found. Set GNOSYS_GLOBAL_XAI_KEY, GNOSYS_XAI_KEY, or XAI_API_KEY.");
470
470
  }
471
471
  return new OpenAICompatibleProvider("xai", model, "https://api.x.ai/v1", apiKey, config);
472
472
  }
473
473
  case "mistral": {
474
474
  const apiKey = getMistralApiKey(config);
475
475
  if (!apiKey) {
476
- throw new Error("No Mistral API key found. Set MISTRAL_API_KEY environment variable or add llm.mistral.apiKey to gnosys.json.");
476
+ throw new Error("No Mistral API key found. Set GNOSYS_GLOBAL_MISTRAL_KEY, GNOSYS_MISTRAL_KEY, or MISTRAL_API_KEY.");
477
477
  }
478
478
  return new OpenAICompatibleProvider("mistral", model, "https://api.mistral.ai/v1", apiKey, config);
479
479
  }
480
+ case "openrouter": {
481
+ const apiKey = getOpenRouterApiKey(config);
482
+ if (!apiKey) {
483
+ throw new Error("No OpenRouter API key found. Set GNOSYS_GLOBAL_OPENROUTER_KEY, GNOSYS_OPENROUTER_KEY, or OPENROUTER_API_KEY.");
484
+ }
485
+ const baseUrl = getOpenRouterBaseUrl(config);
486
+ return new OpenAICompatibleProvider("openrouter", model, baseUrl, apiKey, config);
487
+ }
480
488
  case "custom": {
481
489
  const customConfig = config.llm.custom;
482
- if (!customConfig || !customConfig.baseUrl || !customConfig.model) {
490
+ if (!customConfig?.baseUrl || !customConfig.model) {
483
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");
484
492
  }
485
493
  const apiKey = getCustomApiKey(config);
@@ -552,6 +560,16 @@ export function isProviderAvailable(config, provider) {
552
560
  }
553
561
  return { available: true };
554
562
  }
563
+ case "openrouter": {
564
+ const apiKey = getOpenRouterApiKey(config);
565
+ if (!apiKey) {
566
+ return {
567
+ available: false,
568
+ error: "No OPENROUTER_API_KEY set. Add to environment or gnosys.json.",
569
+ };
570
+ }
571
+ return { available: true };
572
+ }
555
573
  case "custom": {
556
574
  if (!config.llm.custom?.baseUrl || !config.llm.custom?.model) {
557
575
  return {
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Best-effort local-disk detection for master folder setup (v13).
3
+ */
4
+ export declare const LOCAL_DISK_ACK_PHRASE = "LOCAL DISK ONLY";
5
+ export type LocalDiskCheckResult = {
6
+ verdict: "local";
7
+ message: string;
8
+ } | {
9
+ verdict: "network";
10
+ message: string;
11
+ } | {
12
+ verdict: "unknown";
13
+ message: string;
14
+ };
15
+ /** macOS: use `df` filesystem type; Linux: statfs not wired — treat as unknown. */
16
+ export declare function checkMasterPathLocalDisk(folderPath: string): LocalDiskCheckResult;
17
+ export declare function matchesLocalDiskAck(input: string): boolean;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Best-effort local-disk detection for master folder setup (v13).
3
+ */
4
+ import { execSync } from "child_process";
5
+ import path from "path";
6
+ export const LOCAL_DISK_ACK_PHRASE = "LOCAL DISK ONLY";
7
+ /** macOS: use `df` filesystem type; Linux: statfs not wired — treat as unknown. */
8
+ export function checkMasterPathLocalDisk(folderPath) {
9
+ const resolved = path.resolve(folderPath);
10
+ if (process.platform === "darwin") {
11
+ try {
12
+ const out = execSync(`df -T "${resolved}"`, { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] });
13
+ const line = out.trim().split("\n")[1] ?? "";
14
+ const fsType = line.split(/\s+/)[1]?.toLowerCase() ?? "";
15
+ const networkTypes = new Set([
16
+ "smbfs",
17
+ "nfs",
18
+ "afpfs",
19
+ "webdav",
20
+ "fuse",
21
+ "osxfuse",
22
+ "mntfs",
23
+ ]);
24
+ if (networkTypes.has(fsType)) {
25
+ return {
26
+ verdict: "network",
27
+ message: `Path appears to be on a network filesystem (${fsType}). Master DB must be on local disk.`,
28
+ };
29
+ }
30
+ if (fsType === "apfs" || fsType === "hfs" || fsType === "devfs") {
31
+ return { verdict: "local", message: `Path is on local disk (${fsType}).` };
32
+ }
33
+ }
34
+ catch {
35
+ // fall through to unknown
36
+ }
37
+ }
38
+ const lower = resolved.toLowerCase();
39
+ if (lower.includes("/volumes/") &&
40
+ !lower.includes("/volumes/macintosh hd") &&
41
+ !lower.startsWith("/users/")) {
42
+ return {
43
+ verdict: "unknown",
44
+ message: "Path is under /Volumes/ — could be an external or network volume. Cloud-sync folders (Dropbox, iCloud) also look local but corrupt SQLite.",
45
+ };
46
+ }
47
+ return {
48
+ verdict: "unknown",
49
+ message: "Could not verify this path is a plain local disk (not NAS, cloud-sync, or VPN mount).",
50
+ };
51
+ }
52
+ export function matchesLocalDiskAck(input) {
53
+ return input.trim() === LOCAL_DISK_ACK_PHRASE;
54
+ }
@@ -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
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
138
- export function enableWAL(db) {
137
+ export function enableWAL(db, busyTimeoutMs = 5000) {
139
138
  try {
140
139
  db.pragma("journal_mode = WAL");
141
- db.pragma("busy_timeout = 5000"); // Wait up to 5s if DB is busy
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.
@@ -23,17 +23,27 @@
23
23
  * absolute path on a given machine is: `join(roots[root_id], rel_path)` (or a
24
24
  * per-machine override row for projects that live outside any root).
25
25
  */
26
+ /** v13 multi-machine role for this machine (master writer vs staging client). */
27
+ export type MultiMachineRole = "master" | "client";
26
28
  interface MachineRemoteConfig {
27
29
  /** Whether remote sync is configured/active on this machine. */
28
30
  enabled: boolean;
29
- /** Absolute path or URL to the remote DB on this machine (NAS mount / Tailscale). */
31
+ /** Absolute path to the master folder as seen on this machine. */
30
32
  path?: string;
33
+ /** v13: whether this machine hosts the master DB or joins as a client. */
34
+ role?: MultiMachineRole;
31
35
  }
32
36
  export interface MachineConfig {
33
37
  /** Stable random UUID for this machine. Never shared/synced. */
34
38
  machineId: string;
35
39
  /** os.hostname() at write time — used to detect a synced-in foreign file. */
36
40
  hostname: string;
41
+ /**
42
+ * Earlier hostnames this same machine has used (most recent last). Lets the
43
+ * connected-machines registry prune the orphaned entry a rename leaves
44
+ * behind, so a renamed machine doesn't show up twice. Capped to a few.
45
+ */
46
+ previousHostnames?: string[];
37
47
  /** Named root → absolute path on THIS machine. */
38
48
  roots: Record<string, string>;
39
49
  /** Per-machine remote-sync connection. */
@@ -30,6 +30,8 @@ import { randomUUID } from "crypto";
30
30
  import { getMachineConfigPath } from "./paths.js";
31
31
  import { atomicWriteFileSync } from "./atomicWrite.js";
32
32
  const MACHINE_CONFIG_VERSION = 1;
33
+ /** Keep the previous-hostname trail short — we only need it to prune aliases. */
34
+ const MAX_PREVIOUS_HOSTNAMES = 5;
33
35
  /** A fresh machine config for the current host. */
34
36
  export function defaultMachineConfig() {
35
37
  return {
@@ -52,13 +54,21 @@ function normalize(parsed) {
52
54
  roots[k] = path.resolve(v);
53
55
  }
54
56
  }
57
+ const role = parsed.remote?.role === "master" || parsed.remote?.role === "client"
58
+ ? parsed.remote.role
59
+ : undefined;
55
60
  const remote = {
56
61
  enabled: Boolean(parsed.remote?.enabled),
57
62
  ...(typeof parsed.remote?.path === "string" ? { path: parsed.remote.path } : {}),
63
+ ...(role ? { role } : {}),
58
64
  };
65
+ const previousHostnames = Array.isArray(parsed.previousHostnames)
66
+ ? parsed.previousHostnames.filter((h) => typeof h === "string" && h.length > 0)
67
+ : [];
59
68
  return {
60
69
  machineId: typeof parsed.machineId === "string" && parsed.machineId ? parsed.machineId : base.machineId,
61
70
  hostname: typeof parsed.hostname === "string" && parsed.hostname ? parsed.hostname : base.hostname,
71
+ ...(previousHostnames.length > 0 ? { previousHostnames } : {}),
62
72
  roots,
63
73
  remote,
64
74
  schemaVersion: typeof parsed.schemaVersion === "number" ? parsed.schemaVersion : MACHINE_CONFIG_VERSION,
@@ -108,10 +118,16 @@ export function ensureMachineConfig() {
108
118
  return { config: fresh, created: true, regenerated: false };
109
119
  }
110
120
  if (existing.hostname !== host) {
121
+ // Remember the old hostname (dedup, most-recent-last, capped) so the
122
+ // connected-machines registry can prune the entry it left orphaned.
123
+ const trail = (existing.previousHostnames ?? []).filter((h) => h !== existing.hostname && h !== host);
124
+ trail.push(existing.hostname);
125
+ const previousHostnames = trail.slice(-MAX_PREVIOUS_HOSTNAMES);
111
126
  const regenerated = {
112
127
  ...existing,
113
128
  machineId: randomUUID(),
114
129
  hostname: host,
130
+ previousHostnames,
115
131
  };
116
132
  writeMachineConfig(regenerated);
117
133
  return { config: regenerated, created: false, regenerated: true };
@@ -0,0 +1,61 @@
1
+ /**
2
+ * The "connected machines" registry — a small JSON blob stored under the
3
+ * `machines` key in the central DB's `gnosys_meta` table.
4
+ *
5
+ * Each machine records itself here whenever it runs `setup sync-projects`, so
6
+ * the upgrade flow can show which machines share the brain and what version
7
+ * each was last seen on.
8
+ *
9
+ * Historically this map was keyed by `os.hostname()` alone. That made it
10
+ * fragile: if a machine's hostname changed (e.g. macOS renames a laptop from
11
+ * `Edwards-MBP.localdomain` to `EdsMBP`), the machine started recording under
12
+ * the NEW name and the OLD entry was orphaned forever — a "phantom machine"
13
+ * that could never update or be cleaned up.
14
+ *
15
+ * The fix: every entry now also carries the stable `machineId` (from
16
+ * machine.json), and when a machine records itself it prunes any entry whose
17
+ * key matches one of its own previous hostnames (`aliases`). Renames therefore
18
+ * clean themselves up on the next sync. `forgetMachine` covers the one-time
19
+ * case of an already-orphaned phantom from before this tracking existed.
20
+ */
21
+ import type { GnosysDB } from "./db.js";
22
+ export interface MachineRegistryEntry {
23
+ /** Gnosys version this machine was last seen running (at last sync). */
24
+ version: string;
25
+ /** ISO timestamp of that last sync. */
26
+ lastSeen: string;
27
+ /**
28
+ * Stable machine id (machine.json). Optional because entries written by
29
+ * older versions predate this field; recorded going forward.
30
+ */
31
+ machineId?: string;
32
+ }
33
+ /** hostname → entry. */
34
+ export type MachineRegistry = Record<string, MachineRegistryEntry>;
35
+ /** Read and parse the registry, returning {} when absent or malformed. */
36
+ export declare function readMachineRegistry(db: GnosysDB): MachineRegistry;
37
+ /** Persist the registry as JSON. */
38
+ export declare function writeMachineRegistry(db: GnosysDB, registry: MachineRegistry): void;
39
+ export interface RecordMachineInput {
40
+ /** This machine's current hostname (os.hostname()). */
41
+ hostname: string;
42
+ /** Version to stamp for this machine. */
43
+ version: string;
44
+ /** Stable machine id from machine.json. */
45
+ machineId?: string;
46
+ /**
47
+ * Previous hostnames this same machine has used. Any registry entry under
48
+ * one of these names is removed — that's how a rename self-heals.
49
+ */
50
+ aliases?: string[];
51
+ }
52
+ /**
53
+ * Record this machine in the registry, pruning stale alias entries, and
54
+ * persist. Returns the updated registry.
55
+ */
56
+ export declare function recordMachine(db: GnosysDB, input: RecordMachineInput): MachineRegistry;
57
+ /**
58
+ * Remove a machine from the registry by hostname. Returns true if an entry was
59
+ * actually removed. Used by `gnosys machine forget` to clear a phantom.
60
+ */
61
+ export declare function forgetMachine(db: GnosysDB, hostname: string): boolean;
@@ -0,0 +1,80 @@
1
+ /**
2
+ * The "connected machines" registry — a small JSON blob stored under the
3
+ * `machines` key in the central DB's `gnosys_meta` table.
4
+ *
5
+ * Each machine records itself here whenever it runs `setup sync-projects`, so
6
+ * the upgrade flow can show which machines share the brain and what version
7
+ * each was last seen on.
8
+ *
9
+ * Historically this map was keyed by `os.hostname()` alone. That made it
10
+ * fragile: if a machine's hostname changed (e.g. macOS renames a laptop from
11
+ * `Edwards-MBP.localdomain` to `EdsMBP`), the machine started recording under
12
+ * the NEW name and the OLD entry was orphaned forever — a "phantom machine"
13
+ * that could never update or be cleaned up.
14
+ *
15
+ * The fix: every entry now also carries the stable `machineId` (from
16
+ * machine.json), and when a machine records itself it prunes any entry whose
17
+ * key matches one of its own previous hostnames (`aliases`). Renames therefore
18
+ * clean themselves up on the next sync. `forgetMachine` covers the one-time
19
+ * case of an already-orphaned phantom from before this tracking existed.
20
+ */
21
+ const REGISTRY_KEY = "machines";
22
+ /** Read and parse the registry, returning {} when absent or malformed. */
23
+ export function readMachineRegistry(db) {
24
+ try {
25
+ const raw = db.getMeta(REGISTRY_KEY);
26
+ if (!raw)
27
+ return {};
28
+ const parsed = JSON.parse(raw);
29
+ return parsed && typeof parsed === "object" ? parsed : {};
30
+ }
31
+ catch {
32
+ return {};
33
+ }
34
+ }
35
+ /** Persist the registry as JSON. */
36
+ export function writeMachineRegistry(db, registry) {
37
+ db.setMeta(REGISTRY_KEY, JSON.stringify(registry));
38
+ }
39
+ /**
40
+ * Record this machine in the registry, pruning stale alias entries, and
41
+ * persist. Returns the updated registry.
42
+ */
43
+ export function recordMachine(db, input) {
44
+ const registry = readMachineRegistry(db);
45
+ // Drop any orphaned entries left behind by a previous hostname of THIS
46
+ // machine. Only this machine's own aliases are ever passed in, so this can
47
+ // never remove a different physical machine.
48
+ for (const alias of input.aliases ?? []) {
49
+ if (alias && alias !== input.hostname)
50
+ delete registry[alias];
51
+ }
52
+ // Also drop any other entry that shares our stable machineId but lives under
53
+ // a different hostname — covers a rename where the id was preserved.
54
+ if (input.machineId) {
55
+ for (const [host, entry] of Object.entries(registry)) {
56
+ if (host !== input.hostname && entry.machineId === input.machineId) {
57
+ delete registry[host];
58
+ }
59
+ }
60
+ }
61
+ registry[input.hostname] = {
62
+ version: input.version,
63
+ lastSeen: new Date().toISOString(),
64
+ ...(input.machineId ? { machineId: input.machineId } : {}),
65
+ };
66
+ writeMachineRegistry(db, registry);
67
+ return registry;
68
+ }
69
+ /**
70
+ * Remove a machine from the registry by hostname. Returns true if an entry was
71
+ * actually removed. Used by `gnosys machine forget` to clear a phantom.
72
+ */
73
+ export function forgetMachine(db, hostname) {
74
+ const registry = readMachineRegistry(db);
75
+ if (!(hostname in registry))
76
+ return false;
77
+ delete registry[hostname];
78
+ writeMachineRegistry(db, registry);
79
+ return true;
80
+ }
@@ -0,0 +1,8 @@
1
+ import type { GnosysResolver } from "./resolver.js";
2
+ type GetResolver = () => Promise<GnosysResolver>;
3
+ export type MaintainCommandOptions = {
4
+ dryRun?: boolean;
5
+ autoApply?: boolean;
6
+ };
7
+ export declare function runMaintainCommand(getResolver: GetResolver, opts: MaintainCommandOptions): Promise<void>;
8
+ export {};
@@ -0,0 +1,34 @@
1
+ import { loadConfig } from "./config.js";
2
+ export async function runMaintainCommand(getResolver, opts) {
3
+ const { GnosysMaintenanceEngine, formatMaintenanceReport } = await import("./maintenance.js");
4
+ const resolver = await getResolver();
5
+ const stores = resolver.getStores();
6
+ if (stores.length === 0) {
7
+ console.error("No Gnosys stores found. Run gnosys init first.");
8
+ process.exit(1);
9
+ }
10
+ const cfg = await loadConfig(stores[0].path);
11
+ const engine = new GnosysMaintenanceEngine(resolver, cfg);
12
+ const report = await engine.maintain({
13
+ dryRun: opts.dryRun,
14
+ autoApply: opts.autoApply,
15
+ onLog: (level, message) => {
16
+ if (level === "warn") {
17
+ console.error(`⚠ ${message}`);
18
+ }
19
+ else if (level === "action") {
20
+ console.log(`→ ${message}`);
21
+ }
22
+ else {
23
+ console.log(message);
24
+ }
25
+ },
26
+ onProgress: (step, current, total) => {
27
+ process.stdout.write(`\r[${current}/${total}] ${step}...`);
28
+ if (current === total)
29
+ process.stdout.write("\n");
30
+ },
31
+ });
32
+ console.log("");
33
+ console.log(formatMaintenanceReport(report));
34
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * v13 master.json ownership marker — epoch-fenced lease on the master folder.
3
+ */
4
+ export declare const MASTER_MARKER_FILE = "master.json";
5
+ export interface MasterMarker {
6
+ epoch: number;
7
+ holderMachineId: string;
8
+ hostname: string;
9
+ updatedAt: string;
10
+ }
11
+ export declare function masterMarkerPath(masterPath: string): string;
12
+ export declare function readMasterMarker(masterPath: string): MasterMarker | null;
13
+ /** Bump epoch when claiming; reuse epoch+1 on stale takeover. */
14
+ export declare function writeMasterMarker(masterPath: string, machineId: string, opts?: {
15
+ previousEpoch?: number;
16
+ }): MasterMarker;
17
+ /** Heartbeat refresh — same epoch, new updatedAt (transient write failures must not demote). */
18
+ export declare function touchMasterMarkerHeartbeat(masterPath: string): MasterMarker | null;
19
+ export declare function assertMasterLeaseHeld(masterPath: string, machineId: string): void;
20
+ export declare function validateLeaseEpochBeforeWrite(masterPath: string, expectedEpoch: number, machineId: string): void;