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.
- package/dist/cli.js +377 -5162
- package/dist/index.js +542 -244
- package/dist/lib/addCommand.d.ts +9 -0
- package/dist/lib/addCommand.js +102 -0
- package/dist/lib/addStructuredCommand.d.ts +16 -0
- package/dist/lib/addStructuredCommand.js +103 -0
- package/dist/lib/ambiguityCommand.d.ts +4 -0
- package/dist/lib/ambiguityCommand.js +36 -0
- package/dist/lib/apiKeyVault.d.ts +78 -0
- package/dist/lib/apiKeyVault.js +447 -0
- package/dist/lib/archive.js +0 -2
- package/dist/lib/askCommand.d.ts +13 -0
- package/dist/lib/askCommand.js +145 -0
- 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/audioExtract.js +4 -1
- package/dist/lib/auditCommand.d.ts +7 -0
- package/dist/lib/auditCommand.js +27 -0
- package/dist/lib/backupCommand.d.ts +6 -0
- package/dist/lib/backupCommand.js +54 -0
- package/dist/lib/bootstrapCommand.d.ts +15 -0
- package/dist/lib/bootstrapCommand.js +51 -0
- package/dist/lib/briefingCommand.d.ts +7 -0
- package/dist/lib/briefingCommand.js +92 -0
- package/dist/lib/centralizeCommand.d.ts +5 -0
- package/dist/lib/centralizeCommand.js +16 -0
- package/dist/lib/chat/choose.js +2 -2
- package/dist/lib/chatCommand.d.ts +12 -0
- package/dist/lib/chatCommand.js +46 -0
- package/dist/lib/checkCommand.d.ts +4 -0
- package/dist/lib/checkCommand.js +133 -0
- package/dist/lib/clientReadOverlay.d.ts +27 -0
- package/dist/lib/clientReadOverlay.js +76 -0
- package/dist/lib/clientReadResolve.d.ts +32 -0
- package/dist/lib/clientReadResolve.js +84 -0
- package/dist/lib/commitContextCommand.d.ts +9 -0
- package/dist/lib/commitContextCommand.js +142 -0
- package/dist/lib/config.d.ts +41 -48
- package/dist/lib/config.js +58 -57
- package/dist/lib/configCommand.d.ts +10 -0
- package/dist/lib/configCommand.js +321 -0
- package/dist/lib/connectCommand.d.ts +8 -0
- package/dist/lib/connectCommand.js +19 -0
- package/dist/lib/db.d.ts +68 -1
- package/dist/lib/db.js +385 -120
- package/dist/lib/dbWrite.d.ts +1 -1
- package/dist/lib/dearchiveCommand.d.ts +7 -0
- package/dist/lib/dearchiveCommand.js +41 -0
- package/dist/lib/discoverCommand.d.ts +9 -0
- package/dist/lib/discoverCommand.js +87 -0
- package/dist/lib/doctorCommand.d.ts +6 -0
- package/dist/lib/doctorCommand.js +256 -0
- package/dist/lib/docxExtract.js +1 -1
- package/dist/lib/dream.d.ts +50 -2
- package/dist/lib/dream.js +324 -30
- package/dist/lib/dreamCommand.d.ts +10 -0
- package/dist/lib/dreamCommand.js +195 -0
- package/dist/lib/dreamLaunchd.d.ts +2 -0
- package/dist/lib/dreamLaunchd.js +72 -0
- package/dist/lib/dreamLogCommand.d.ts +10 -0
- package/dist/lib/dreamLogCommand.js +58 -0
- package/dist/lib/dreamReport.d.ts +7 -0
- package/dist/lib/dreamReport.js +114 -0
- package/dist/lib/dreamRunLog.d.ts +121 -0
- package/dist/lib/dreamRunLog.js +234 -0
- package/dist/lib/embeddings.js +3 -3
- package/dist/lib/exportCommand.d.ts +18 -0
- package/dist/lib/exportCommand.js +101 -0
- 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/fsearchCommand.d.ts +8 -0
- package/dist/lib/fsearchCommand.js +44 -0
- package/dist/lib/graphCommand.d.ts +4 -0
- package/dist/lib/graphCommand.js +68 -0
- package/dist/lib/helperGenerateCommand.d.ts +5 -0
- package/dist/lib/helperGenerateCommand.js +27 -0
- package/dist/lib/historyCommand.d.ts +5 -0
- package/dist/lib/historyCommand.js +51 -0
- package/dist/lib/hybridSearchCommand.d.ts +12 -0
- package/dist/lib/hybridSearchCommand.js +95 -0
- package/dist/lib/importCommand.d.ts +16 -0
- package/dist/lib/importCommand.js +89 -0
- package/dist/lib/importProject.js +2 -1
- package/dist/lib/importProjectCommand.d.ts +6 -0
- package/dist/lib/importProjectCommand.js +43 -0
- package/dist/lib/ingestCommand.d.ts +13 -0
- package/dist/lib/ingestCommand.js +95 -0
- package/dist/lib/installOutput.d.ts +36 -0
- package/dist/lib/installOutput.js +55 -0
- package/dist/lib/lensCommand.d.ts +20 -0
- package/dist/lib/lensCommand.js +61 -0
- package/dist/lib/lensing.d.ts +1 -0
- package/dist/lib/lensing.js +50 -9
- package/dist/lib/linksCommand.d.ts +7 -0
- package/dist/lib/linksCommand.js +48 -0
- package/dist/lib/listCommand.d.ts +8 -0
- package/dist/lib/listCommand.js +74 -0
- package/dist/lib/llm.d.ts +1 -1
- package/dist/lib/llm.js +27 -9
- package/dist/lib/localDiskCheck.d.ts +17 -0
- package/dist/lib/localDiskCheck.js +54 -0
- package/dist/lib/lock.d.ts +1 -1
- package/dist/lib/lock.js +5 -3
- package/dist/lib/machineConfig.d.ts +11 -1
- package/dist/lib/machineConfig.js +16 -0
- package/dist/lib/machineRegistry.d.ts +61 -0
- package/dist/lib/machineRegistry.js +80 -0
- package/dist/lib/maintainCommand.d.ts +8 -0
- package/dist/lib/maintainCommand.js +34 -0
- package/dist/lib/masterLease.d.ts +20 -0
- package/dist/lib/masterLease.js +68 -0
- package/dist/lib/migrate.js +0 -1
- package/dist/lib/migrateCommand.d.ts +7 -0
- package/dist/lib/migrateCommand.js +158 -0
- package/dist/lib/migrateDbCommand.d.ts +9 -0
- package/dist/lib/migrateDbCommand.js +94 -0
- package/dist/lib/modelValidation.d.ts +5 -0
- package/dist/lib/modelValidation.js +27 -0
- package/dist/lib/multimodalIngest.js +1 -1
- package/dist/lib/openrouterTiers.d.ts +29 -0
- package/dist/lib/openrouterTiers.js +113 -0
- package/dist/lib/platform.d.ts +0 -6
- package/dist/lib/platform.js +0 -28
- package/dist/lib/prefCommand.d.ts +10 -0
- package/dist/lib/prefCommand.js +118 -0
- package/dist/lib/projectsCommand.d.ts +8 -0
- package/dist/lib/projectsCommand.js +131 -0
- package/dist/lib/readCommand.d.ts +7 -0
- package/dist/lib/readCommand.js +63 -0
- package/dist/lib/recall.d.ts +3 -0
- package/dist/lib/recall.js +19 -4
- package/dist/lib/recallCommand.d.ts +11 -0
- package/dist/lib/recallCommand.js +112 -0
- package/dist/lib/reflectCommand.d.ts +8 -0
- package/dist/lib/reflectCommand.js +61 -0
- package/dist/lib/reindexCommand.d.ts +4 -0
- package/dist/lib/reindexCommand.js +34 -0
- package/dist/lib/reindexGraphCommand.d.ts +4 -0
- package/dist/lib/reindexGraphCommand.js +12 -0
- package/dist/lib/reinforceCommand.d.ts +8 -0
- package/dist/lib/reinforceCommand.js +40 -0
- package/dist/lib/remote.d.ts +5 -1
- package/dist/lib/remote.js +5 -1
- package/dist/lib/remoteWizard.d.ts +24 -5
- package/dist/lib/remoteWizard.js +308 -319
- package/dist/lib/restoreCommand.d.ts +5 -0
- package/dist/lib/restoreCommand.js +35 -0
- package/dist/lib/rulesGen.d.ts +8 -0
- package/dist/lib/rulesGen.js +16 -0
- package/dist/lib/sandboxStartCommand.d.ts +6 -0
- package/dist/lib/sandboxStartCommand.js +25 -0
- package/dist/lib/sandboxStatusCommand.d.ts +4 -0
- package/dist/lib/sandboxStatusCommand.js +24 -0
- package/dist/lib/sandboxStopCommand.d.ts +4 -0
- package/dist/lib/sandboxStopCommand.js +21 -0
- package/dist/lib/search.d.ts +0 -2
- package/dist/lib/search.js +0 -7
- package/dist/lib/searchCommand.d.ts +9 -0
- package/dist/lib/searchCommand.js +90 -0
- package/dist/lib/semanticSearchCommand.d.ts +8 -0
- package/dist/lib/semanticSearchCommand.js +52 -0
- package/dist/lib/setup/configSetRender.js +2 -0
- package/dist/lib/setup/providerGlyphs.d.ts +19 -0
- package/dist/lib/setup/providerGlyphs.js +42 -0
- package/dist/lib/setup/remoteRender.d.ts +31 -1
- package/dist/lib/setup/remoteRender.js +95 -4
- package/dist/lib/setup/sections/providers.d.ts +17 -0
- package/dist/lib/setup/sections/providers.js +307 -0
- package/dist/lib/setup/sections/routing.d.ts +2 -6
- package/dist/lib/setup/sections/routing.js +67 -82
- package/dist/lib/setup/sections/taskRoutingEditor.d.ts +13 -0
- package/dist/lib/setup/sections/taskRoutingEditor.js +139 -0
- package/dist/lib/setup/summary.d.ts +9 -0
- package/dist/lib/setup/summary.js +51 -37
- package/dist/lib/setup/ui/header.js +0 -1
- package/dist/lib/setup.d.ts +105 -15
- package/dist/lib/setup.js +747 -287
- package/dist/lib/setupKeys.d.ts +42 -0
- package/dist/lib/setupKeys.js +564 -0
- package/dist/lib/setupRemoteCommand.d.ts +4 -0
- package/dist/lib/setupRemoteCommand.js +28 -0
- package/dist/lib/setupRemotePullCommand.d.ts +5 -0
- package/dist/lib/setupRemotePullCommand.js +52 -0
- package/dist/lib/setupRemotePushCommand.d.ts +5 -0
- package/dist/lib/setupRemotePushCommand.js +57 -0
- package/dist/lib/setupRemoteResolveCommand.d.ts +4 -0
- package/dist/lib/setupRemoteResolveCommand.js +48 -0
- package/dist/lib/setupRemoteStatusCommand.d.ts +4 -0
- package/dist/lib/setupRemoteStatusCommand.js +73 -0
- package/dist/lib/setupRemoteSyncCommand.d.ts +6 -0
- package/dist/lib/setupRemoteSyncCommand.js +65 -0
- package/dist/lib/setupSyncProjectsCommand.d.ts +4 -0
- package/dist/lib/setupSyncProjectsCommand.js +292 -0
- package/dist/lib/staleCommand.d.ts +8 -0
- package/dist/lib/staleCommand.js +34 -0
- package/dist/lib/statsCommand.d.ts +6 -0
- package/dist/lib/statsCommand.js +142 -0
- package/dist/lib/statusCommand.d.ts +18 -0
- package/dist/lib/statusCommand.js +250 -0
- package/dist/lib/storesCommand.d.ts +2 -0
- package/dist/lib/storesCommand.js +4 -0
- package/dist/lib/syncClient.d.ts +41 -0
- package/dist/lib/syncClient.js +234 -0
- package/dist/lib/syncCommand.d.ts +6 -0
- package/dist/lib/syncCommand.js +57 -0
- package/dist/lib/syncDoctorCommand.d.ts +5 -0
- package/dist/lib/syncDoctorCommand.js +100 -0
- package/dist/lib/syncIngest.d.ts +30 -0
- package/dist/lib/syncIngest.js +175 -0
- package/dist/lib/syncIngestLaunchd.d.ts +8 -0
- package/dist/lib/syncIngestLaunchd.js +93 -0
- package/dist/lib/syncIngestStartup.d.ts +5 -0
- package/dist/lib/syncIngestStartup.js +29 -0
- package/dist/lib/syncIngestSystemd.d.ts +10 -0
- package/dist/lib/syncIngestSystemd.js +97 -0
- package/dist/lib/syncIngestTimer.d.ts +8 -0
- package/dist/lib/syncIngestTimer.js +27 -0
- package/dist/lib/syncIngestTimerCommand.d.ts +7 -0
- package/dist/lib/syncIngestTimerCommand.js +83 -0
- package/dist/lib/syncLock.d.ts +6 -0
- package/dist/lib/syncLock.js +74 -0
- package/dist/lib/syncSnapshot.d.ts +32 -0
- package/dist/lib/syncSnapshot.js +188 -0
- package/dist/lib/syncStaging.d.ts +79 -0
- package/dist/lib/syncStaging.js +237 -0
- package/dist/lib/tagsAddCommand.d.ts +8 -0
- package/dist/lib/tagsAddCommand.js +18 -0
- package/dist/lib/tagsCommand.d.ts +4 -0
- package/dist/lib/tagsCommand.js +16 -0
- package/dist/lib/timelineCommand.d.ts +7 -0
- package/dist/lib/timelineCommand.js +49 -0
- package/dist/lib/traceCommand.d.ts +6 -0
- package/dist/lib/traceCommand.js +39 -0
- package/dist/lib/traverseCommand.d.ts +6 -0
- package/dist/lib/traverseCommand.js +58 -0
- package/dist/lib/updateCommand.d.ts +13 -0
- package/dist/lib/updateCommand.js +67 -0
- package/dist/lib/updateStatusCommand.d.ts +5 -0
- package/dist/lib/updateStatusCommand.js +38 -0
- package/dist/lib/webAddCommand.d.ts +8 -0
- package/dist/lib/webAddCommand.js +55 -0
- package/dist/lib/webBuildCommand.d.ts +10 -0
- package/dist/lib/webBuildCommand.js +65 -0
- package/dist/lib/webBuildIndexCommand.d.ts +8 -0
- package/dist/lib/webBuildIndexCommand.js +37 -0
- package/dist/lib/webIndex.js +0 -1
- package/dist/lib/webIngestCommand.d.ts +11 -0
- package/dist/lib/webIngestCommand.js +51 -0
- package/dist/lib/webInitCommand.d.ts +9 -0
- package/dist/lib/webInitCommand.js +167 -0
- package/dist/lib/webRemoveCommand.d.ts +5 -0
- package/dist/lib/webRemoveCommand.js +41 -0
- package/dist/lib/webStatusCommand.d.ts +5 -0
- package/dist/lib/webStatusCommand.js +94 -0
- package/dist/lib/webUpdateCommand.d.ts +7 -0
- package/dist/lib/webUpdateCommand.js +72 -0
- package/dist/lib/workingSetCommand.d.ts +6 -0
- package/dist/lib/workingSetCommand.js +37 -0
- package/dist/sandbox/client.js +1 -1
- package/dist/sandbox/manager.js +1 -14
- package/dist/sandbox/server.js +3 -5
- package/package.json +6 -2
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v13 client-side sync: reachability heartbeat, offline snapshot rule, pending overlay.
|
|
3
|
+
*/
|
|
4
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "fs";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import { GnosysDB } from "./db.js";
|
|
7
|
+
import { readMachineConfig, getMachineId } from "./machineConfig.js";
|
|
8
|
+
import { getConfiguredRemotePath } from "./remote.js";
|
|
9
|
+
import { acceptClientSnapshot, clientSnapshotStore, compareSnapshotVersion, formatSnapshotAge, getClientAcceptedManifest, getMasterManifest, } from "./syncSnapshot.js";
|
|
10
|
+
import { countFailedStagingFiles, machineStagingDir, stagingRoot } from "./syncStaging.js";
|
|
11
|
+
import { renderClientSyncStatusLines, } from "./setup/remoteRender.js";
|
|
12
|
+
const REACHABILITY_TTL_MS = 30_000;
|
|
13
|
+
function readManifestHeartbeat(masterPath) {
|
|
14
|
+
const manifestPath = path.join(masterPath, "snapshots", "snapshot-manifest.json");
|
|
15
|
+
if (!existsSync(manifestPath))
|
|
16
|
+
return null;
|
|
17
|
+
try {
|
|
18
|
+
const m = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
19
|
+
return m.publishedAt ?? null;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function isMasterReachable(masterPath) {
|
|
26
|
+
try {
|
|
27
|
+
const manifestPath = path.join(masterPath, "snapshots", "snapshot-manifest.json");
|
|
28
|
+
if (existsSync(manifestPath)) {
|
|
29
|
+
const mtime = statSync(manifestPath).mtimeMs;
|
|
30
|
+
if (Date.now() - mtime < REACHABILITY_TTL_MS)
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
const dbPath = path.join(masterPath, "gnosys.db");
|
|
34
|
+
if (existsSync(dbPath)) {
|
|
35
|
+
statSync(dbPath);
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
statSync(masterPath);
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export function countClientWaitingStaging(masterPath, machineId) {
|
|
46
|
+
const dir = machineStagingDir(masterPath, machineId);
|
|
47
|
+
if (!existsSync(dir))
|
|
48
|
+
return 0;
|
|
49
|
+
try {
|
|
50
|
+
return readdirSync(dir).filter((n) => n.endsWith(".json")).length;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return 0;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
export function listClientReceipts(masterPath, machineId) {
|
|
57
|
+
const dir = path.join(stagingRoot(masterPath), machineId, "receipts");
|
|
58
|
+
if (!existsSync(dir))
|
|
59
|
+
return [];
|
|
60
|
+
const receipts = [];
|
|
61
|
+
for (const file of readdirSync(dir).filter((f) => f.endsWith(".json"))) {
|
|
62
|
+
try {
|
|
63
|
+
const raw = readFileSync(path.join(dir, file), "utf-8");
|
|
64
|
+
receipts.push(JSON.parse(raw));
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// skip malformed
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return receipts;
|
|
71
|
+
}
|
|
72
|
+
export function getIngestedUlids(masterPath, machineId) {
|
|
73
|
+
return new Set(listClientReceipts(masterPath, machineId).map((r) => r.ulid));
|
|
74
|
+
}
|
|
75
|
+
export function openClientReadContext(localDb, masterPath, machineId) {
|
|
76
|
+
const mc = readMachineConfig();
|
|
77
|
+
const role = mc?.remote.role;
|
|
78
|
+
if (!role || role === "master") {
|
|
79
|
+
return {
|
|
80
|
+
db: localDb,
|
|
81
|
+
localDb,
|
|
82
|
+
pendingOverlay: [],
|
|
83
|
+
source: "master",
|
|
84
|
+
masterReachable: true,
|
|
85
|
+
ownsReadDb: false,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
const reachable = isMasterReachable(masterPath);
|
|
89
|
+
const ingestedUlids = getIngestedUlids(masterPath, machineId);
|
|
90
|
+
const pendingAdds = localDb.listActivePendingAdds().filter((p) => !ingestedUlids.has(p.id));
|
|
91
|
+
const store = clientSnapshotStore(masterPath);
|
|
92
|
+
const snapPath = path.join(store, "gnosys.db");
|
|
93
|
+
if (reachable) {
|
|
94
|
+
// v13 completion (5.12.x): clients must not open the live gnosys.db over
|
|
95
|
+
// the network (concurrent master writes + network FS = torn-page hazard,
|
|
96
|
+
// DESIGN.md "The Simple Rule"). Refresh the verified local snapshot copy
|
|
97
|
+
// when the master has published a newer one, then read the local copy.
|
|
98
|
+
const manifest = getMasterManifest(masterPath);
|
|
99
|
+
if (manifest) {
|
|
100
|
+
const accepted = getClientAcceptedManifest(masterPath);
|
|
101
|
+
if (compareSnapshotVersion(accepted, manifest)) {
|
|
102
|
+
// Best effort — on checksum/copy failure we fall through to whatever
|
|
103
|
+
// local snapshot (or live-DB fallback) is still available.
|
|
104
|
+
acceptClientSnapshot(masterPath, manifest);
|
|
105
|
+
}
|
|
106
|
+
if (existsSync(snapPath)) {
|
|
107
|
+
const snapDb = new GnosysDB(store);
|
|
108
|
+
if (snapDb.isAvailable()) {
|
|
109
|
+
return {
|
|
110
|
+
db: snapDb,
|
|
111
|
+
localDb,
|
|
112
|
+
pendingOverlay: pendingAdds,
|
|
113
|
+
source: "snapshot",
|
|
114
|
+
masterReachable: true,
|
|
115
|
+
ownsReadDb: true,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
snapDb.close();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Compatibility fallback: master has never published a snapshot (older
|
|
122
|
+
// master version, or first sweep still pending) — open the live DB as
|
|
123
|
+
// before. Disappears once the master runs one publish-enabled sweep.
|
|
124
|
+
const masterDb = new GnosysDB(masterPath);
|
|
125
|
+
if (masterDb.isAvailable()) {
|
|
126
|
+
return {
|
|
127
|
+
db: masterDb,
|
|
128
|
+
localDb,
|
|
129
|
+
pendingOverlay: pendingAdds,
|
|
130
|
+
source: "master",
|
|
131
|
+
masterReachable: true,
|
|
132
|
+
ownsReadDb: true,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
masterDb.close();
|
|
136
|
+
}
|
|
137
|
+
// Offline (soft rule, signed off 2026-06-11): the last-accepted snapshot
|
|
138
|
+
// stays readable — immutable + checksummed, and offline clients are
|
|
139
|
+
// add-only, so staleness is the only risk. Status surfaces snapshotAge.
|
|
140
|
+
if (existsSync(snapPath)) {
|
|
141
|
+
const snapDb = new GnosysDB(store);
|
|
142
|
+
if (snapDb.isAvailable()) {
|
|
143
|
+
return {
|
|
144
|
+
db: snapDb,
|
|
145
|
+
localDb,
|
|
146
|
+
pendingOverlay: pendingAdds,
|
|
147
|
+
source: "snapshot",
|
|
148
|
+
masterReachable: false,
|
|
149
|
+
ownsReadDb: true,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
snapDb.close();
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
db: localDb,
|
|
156
|
+
localDb,
|
|
157
|
+
pendingOverlay: pendingAdds,
|
|
158
|
+
source: "pending-only",
|
|
159
|
+
masterReachable: false,
|
|
160
|
+
ownsReadDb: false,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
/** Release snapshot/master DB handles opened by openClientReadContext. */
|
|
164
|
+
export function closeClientReadContext(ctx) {
|
|
165
|
+
if (ctx.ownsReadDb) {
|
|
166
|
+
ctx.db.close();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
export function getV13SyncStatus(localDb) {
|
|
170
|
+
const mc = readMachineConfig();
|
|
171
|
+
const masterPath = getConfiguredRemotePath(localDb);
|
|
172
|
+
const role = mc?.remote.role ?? null;
|
|
173
|
+
const empty = {
|
|
174
|
+
role,
|
|
175
|
+
masterPath,
|
|
176
|
+
masterReachable: false,
|
|
177
|
+
waitingToSync: 0,
|
|
178
|
+
failedToSync: 0,
|
|
179
|
+
pendingOfflineAdds: 0,
|
|
180
|
+
snapshotAge: null,
|
|
181
|
+
lines: [],
|
|
182
|
+
};
|
|
183
|
+
if (!masterPath || !role)
|
|
184
|
+
return empty;
|
|
185
|
+
const reachable = isMasterReachable(masterPath);
|
|
186
|
+
const machineId = getMachineId();
|
|
187
|
+
let waitingToSync = 0;
|
|
188
|
+
let failedToSync = 0;
|
|
189
|
+
if (role === "client") {
|
|
190
|
+
waitingToSync = countClientWaitingStaging(masterPath, machineId);
|
|
191
|
+
failedToSync = countFailedStagingFiles(masterPath, machineId);
|
|
192
|
+
}
|
|
193
|
+
else if (role === "master") {
|
|
194
|
+
const masterDb = new GnosysDB(masterPath);
|
|
195
|
+
if (masterDb.isAvailable()) {
|
|
196
|
+
waitingToSync = masterDb.countPendingStagingLedger();
|
|
197
|
+
}
|
|
198
|
+
masterDb.close();
|
|
199
|
+
}
|
|
200
|
+
let pendingOfflineAdds = 0;
|
|
201
|
+
if (localDb.isAvailable()) {
|
|
202
|
+
pendingOfflineAdds = localDb.listActivePendingAdds().length;
|
|
203
|
+
}
|
|
204
|
+
let snapshotAge = null;
|
|
205
|
+
const accepted = getClientAcceptedManifest(masterPath);
|
|
206
|
+
if (accepted?.publishedAt) {
|
|
207
|
+
snapshotAge = formatSnapshotAge(accepted.publishedAt);
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
const pub = readManifestHeartbeat(masterPath);
|
|
211
|
+
if (pub)
|
|
212
|
+
snapshotAge = formatSnapshotAge(pub);
|
|
213
|
+
}
|
|
214
|
+
const input = {
|
|
215
|
+
masterReachable: reachable,
|
|
216
|
+
waitingToSync,
|
|
217
|
+
failedToSync,
|
|
218
|
+
pendingOfflineAdds: reachable ? undefined : pendingOfflineAdds,
|
|
219
|
+
};
|
|
220
|
+
const lines = renderClientSyncStatusLines(input);
|
|
221
|
+
if (snapshotAge && reachable) {
|
|
222
|
+
lines.push(`Snapshot as of ${snapshotAge}`);
|
|
223
|
+
}
|
|
224
|
+
return {
|
|
225
|
+
role,
|
|
226
|
+
masterPath,
|
|
227
|
+
masterReachable: reachable,
|
|
228
|
+
waitingToSync,
|
|
229
|
+
failedToSync,
|
|
230
|
+
pendingOfflineAdds,
|
|
231
|
+
snapshotAge,
|
|
232
|
+
lines,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { GnosysDB } from "./db.js";
|
|
3
|
+
import { readProjectIdentity } from "./projectIdentity.js";
|
|
4
|
+
import { syncToTarget } from "./rulesGen.js";
|
|
5
|
+
export async function runSyncCommand(opts) {
|
|
6
|
+
const projectDir = opts.directory ? path.resolve(opts.directory) : process.cwd();
|
|
7
|
+
const target = opts.global ? "global" : (opts.target || null);
|
|
8
|
+
let centralDb = null;
|
|
9
|
+
try {
|
|
10
|
+
centralDb = GnosysDB.openCentral();
|
|
11
|
+
if (!centralDb.isAvailable()) {
|
|
12
|
+
console.error("Central DB not available (better-sqlite3 missing).");
|
|
13
|
+
process.exitCode = 1;
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
if (target === "global") {
|
|
17
|
+
const results = await syncToTarget(centralDb, projectDir, "global", null);
|
|
18
|
+
for (const result of results) {
|
|
19
|
+
const action = result.created ? "Created" : "Updated";
|
|
20
|
+
console.log(`${action} global rules: ${result.filePath}`);
|
|
21
|
+
console.log(` Preferences injected: ${result.prefCount}`);
|
|
22
|
+
}
|
|
23
|
+
console.log(`\nContent is inside <!-- GNOSYS:START --> / <!-- GNOSYS:END --> markers.`);
|
|
24
|
+
console.log(`User content outside these markers is preserved.`);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const identity = await readProjectIdentity(projectDir);
|
|
28
|
+
if (!identity) {
|
|
29
|
+
console.error("No project identity found. Run 'gnosys init' first.");
|
|
30
|
+
process.exitCode = 1;
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const resolvedTarget = target || identity.agentRulesTarget || "all";
|
|
34
|
+
const results = await syncToTarget(centralDb, projectDir, resolvedTarget, identity.projectId);
|
|
35
|
+
if (results.length === 0) {
|
|
36
|
+
console.error("No targets found. Create a CLAUDE.md, .cursor/, or .codex/ directory first.");
|
|
37
|
+
process.exitCode = 1;
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
for (const result of results) {
|
|
41
|
+
const action = result.created ? "Created" : "Updated";
|
|
42
|
+
console.log(`${action} rules file: ${result.filePath}`);
|
|
43
|
+
console.log(` Preferences injected: ${result.prefCount}`);
|
|
44
|
+
console.log(` Project conventions: ${result.conventionCount}`);
|
|
45
|
+
}
|
|
46
|
+
console.log(`\nContent is inside <!-- GNOSYS:START --> / <!-- GNOSYS:END --> markers.`);
|
|
47
|
+
console.log(`User content outside these markers is preserved.`);
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
console.error(`Error: ${err instanceof Error ? err.message : err}`);
|
|
51
|
+
process.exitCode = 1;
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
centralDb?.close();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { GnosysDB } from "./db.js";
|
|
2
|
+
import { getConfiguredRemotePath } from "./remote.js";
|
|
3
|
+
import { readMachineConfig } from "./machineConfig.js";
|
|
4
|
+
import { getV13SyncStatus } from "./syncClient.js";
|
|
5
|
+
import { runMasterIngestSweepAndPublish } from "./syncIngest.js";
|
|
6
|
+
import { getSyncIngestTimerStatus } from "./syncIngestTimer.js";
|
|
7
|
+
import { quarantineStaleTmpFiles, stagingRoot } from "./syncStaging.js";
|
|
8
|
+
import { existsSync, readdirSync } from "fs";
|
|
9
|
+
import { readMasterMarker } from "./masterLease.js";
|
|
10
|
+
import { printStatus } from "./setup/ui/status.js";
|
|
11
|
+
export async function runSyncDoctorCommand(opts) {
|
|
12
|
+
let db = null;
|
|
13
|
+
try {
|
|
14
|
+
db = GnosysDB.openLocal();
|
|
15
|
+
if (!db.isAvailable()) {
|
|
16
|
+
console.error("Central DB not available.");
|
|
17
|
+
process.exitCode = 1;
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const masterPath = getConfiguredRemotePath(db);
|
|
21
|
+
const mc = readMachineConfig();
|
|
22
|
+
if (!masterPath || !mc?.remote.enabled) {
|
|
23
|
+
console.log("Multi-machine sync is not configured.");
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const status = getV13SyncStatus(db);
|
|
27
|
+
const marker = readMasterMarker(masterPath);
|
|
28
|
+
const timerStatus = getSyncIngestTimerStatus();
|
|
29
|
+
const report = {
|
|
30
|
+
role: status.role,
|
|
31
|
+
masterPath,
|
|
32
|
+
masterReachable: status.masterReachable,
|
|
33
|
+
waitingToSync: status.waitingToSync,
|
|
34
|
+
failedToSync: status.failedToSync,
|
|
35
|
+
pendingOfflineAdds: status.pendingOfflineAdds,
|
|
36
|
+
masterEpoch: marker?.epoch ?? null,
|
|
37
|
+
snapshotAge: status.snapshotAge,
|
|
38
|
+
timerInstalled: timerStatus.installed,
|
|
39
|
+
timerPath: timerStatus.path || null,
|
|
40
|
+
timerPlatform: timerStatus.platform,
|
|
41
|
+
};
|
|
42
|
+
let ingestResult = null;
|
|
43
|
+
if (opts.ingest && mc.remote.role === "master") {
|
|
44
|
+
const root = stagingRoot(masterPath);
|
|
45
|
+
if (existsSync(root)) {
|
|
46
|
+
for (const id of readdirSync(root)) {
|
|
47
|
+
if (!id.startsWith("."))
|
|
48
|
+
quarantineStaleTmpFiles(masterPath, id);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
ingestResult = await runMasterIngestSweepAndPublish(masterPath, {
|
|
52
|
+
quiet: opts.quiet || !!opts.json,
|
|
53
|
+
});
|
|
54
|
+
if (ingestResult.errors.length > 0) {
|
|
55
|
+
process.exitCode = 1;
|
|
56
|
+
}
|
|
57
|
+
if (opts.json) {
|
|
58
|
+
report.ingest = ingestResult;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (opts.json) {
|
|
62
|
+
console.log(JSON.stringify(report, null, 2));
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
console.log("\nGnosys sync doctor (v13)\n");
|
|
66
|
+
for (const line of status.lines)
|
|
67
|
+
console.log(line);
|
|
68
|
+
if (marker) {
|
|
69
|
+
console.log(`\nMaster lease: epoch ${marker.epoch}, holder ${marker.holderMachineId}`);
|
|
70
|
+
}
|
|
71
|
+
if (timerStatus.installed) {
|
|
72
|
+
console.log(`\nIngest timer: installed (${timerStatus.path})`);
|
|
73
|
+
}
|
|
74
|
+
else if (timerStatus.platform === "unsupported") {
|
|
75
|
+
console.log("\nIngest timer: not available on this platform (Windows — use doctor --ingest manually)");
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
console.log("\nIngest timer: not installed");
|
|
79
|
+
console.log("Install with: gnosys setup remote timer --install");
|
|
80
|
+
}
|
|
81
|
+
if (status.failedToSync > 0) {
|
|
82
|
+
printStatus("progress", "remediation", "inspect master-folder/.gnosys-staging/*/failed/");
|
|
83
|
+
}
|
|
84
|
+
if (!status.masterReachable) {
|
|
85
|
+
printStatus("warn", "mount or VPN path to master folder, then re-run doctor");
|
|
86
|
+
}
|
|
87
|
+
if (opts.ingest && mc.remote.role === "master" && ingestResult && !opts.quiet) {
|
|
88
|
+
console.log(`\nIngest sweep: +${ingestResult.ingested} ingested, ${ingestResult.skipped} skipped, ${ingestResult.quarantined} quarantined`);
|
|
89
|
+
for (const e of ingestResult.errors)
|
|
90
|
+
printStatus("fail", e);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
console.error(`Error: ${err instanceof Error ? err.message : err}`);
|
|
95
|
+
process.exitCode = 1;
|
|
96
|
+
}
|
|
97
|
+
finally {
|
|
98
|
+
db?.close();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v13 master ingest sweep — process staged JSON into gnosys.db.
|
|
3
|
+
*/
|
|
4
|
+
export interface IngestSweepResult {
|
|
5
|
+
ingested: number;
|
|
6
|
+
skipped: number;
|
|
7
|
+
quarantined: number;
|
|
8
|
+
errors: string[];
|
|
9
|
+
}
|
|
10
|
+
export interface IngestSweepOptions {
|
|
11
|
+
/** Suppress stdout (for timer / background contexts). */
|
|
12
|
+
quiet?: boolean;
|
|
13
|
+
/** Write a single JSON object to stdout with the sweep result. */
|
|
14
|
+
json?: boolean;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Always-on cheap ingest sweep (agent start + timer). Single-writer lock on local disk.
|
|
18
|
+
*/
|
|
19
|
+
export declare function runMasterIngestSweep(masterPath: string, opts?: IngestSweepOptions): IngestSweepResult;
|
|
20
|
+
/**
|
|
21
|
+
* v13 completion (5.12.x): sweep, then publish a fresh immutable snapshot
|
|
22
|
+
* when the sweep changed the DB (or none has ever been published). Clients
|
|
23
|
+
* read the published snapshot via a verified local copy instead of opening
|
|
24
|
+
* the live gnosys.db over the network — the hazard the design forbids.
|
|
25
|
+
*
|
|
26
|
+
* Kept separate from runMasterIngestSweep: publishMasterSnapshot acquires
|
|
27
|
+
* the same master-ingest lock, so it must run after the sweep releases it,
|
|
28
|
+
* and the sweep itself stays synchronous for existing callers.
|
|
29
|
+
*/
|
|
30
|
+
export declare function runMasterIngestSweepAndPublish(masterPath: string, opts?: IngestSweepOptions): Promise<IngestSweepResult>;
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v13 master ingest sweep — process staged JSON into gnosys.db.
|
|
3
|
+
*/
|
|
4
|
+
import { existsSync, readdirSync, unlinkSync, writeFileSync, mkdirSync } from "fs";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import { GnosysDB, fnv1a } from "./db.js";
|
|
7
|
+
import { getGnosysHome } from "./paths.js";
|
|
8
|
+
import { ensureMachineConfig } from "./machineConfig.js";
|
|
9
|
+
import { listPendingStagingQueue, observeStagingFile, parseStagedFile, quarantineStagingFile, quarantineStaleTmpFiles, stagingRoot, verifyMemoryExistsInDb, } from "./syncStaging.js";
|
|
10
|
+
import { assertMasterLeaseHeld, readMasterMarker, touchMasterMarkerHeartbeat, validateLeaseEpochBeforeWrite, } from "./masterLease.js";
|
|
11
|
+
import { acquireWriteLockSync, } from "./syncLock.js";
|
|
12
|
+
const INGEST_LOCK_NAME = "master-ingest.lock";
|
|
13
|
+
function payloadToMemory(p, now) {
|
|
14
|
+
const contentHash = fnv1a(`${p.title}\n${p.content}`);
|
|
15
|
+
return {
|
|
16
|
+
id: p.id,
|
|
17
|
+
title: p.title,
|
|
18
|
+
category: p.category,
|
|
19
|
+
content: p.content,
|
|
20
|
+
summary: null,
|
|
21
|
+
tags: JSON.stringify(p.tags),
|
|
22
|
+
relevance: "",
|
|
23
|
+
author: "user",
|
|
24
|
+
authority: "user",
|
|
25
|
+
confidence: 0.8,
|
|
26
|
+
reinforcement_count: 0,
|
|
27
|
+
content_hash: contentHash,
|
|
28
|
+
status: "active",
|
|
29
|
+
tier: "active",
|
|
30
|
+
supersedes: null,
|
|
31
|
+
superseded_by: null,
|
|
32
|
+
last_reinforced: null,
|
|
33
|
+
created: p.writtenAt || now,
|
|
34
|
+
modified: now,
|
|
35
|
+
source_path: null,
|
|
36
|
+
project_id: p.project_id,
|
|
37
|
+
scope: p.scope || "project",
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function writeIngestReceipt(masterPath, machineId, ulid, outcome) {
|
|
41
|
+
const dir = path.join(stagingRoot(masterPath), machineId, "receipts");
|
|
42
|
+
mkdirSync(dir, { recursive: true });
|
|
43
|
+
writeFileSync(path.join(dir, `${ulid}.json`), JSON.stringify({ ulid, outcome, at: new Date().toISOString() }, null, 2) + "\n");
|
|
44
|
+
}
|
|
45
|
+
function listMachineIds(masterPath) {
|
|
46
|
+
const root = stagingRoot(masterPath);
|
|
47
|
+
if (!existsSync(root))
|
|
48
|
+
return [];
|
|
49
|
+
return readdirSync(root).filter((name) => {
|
|
50
|
+
if (name.startsWith("."))
|
|
51
|
+
return false;
|
|
52
|
+
try {
|
|
53
|
+
return existsSync(path.join(root, name));
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Always-on cheap ingest sweep (agent start + timer). Single-writer lock on local disk.
|
|
62
|
+
*/
|
|
63
|
+
export function runMasterIngestSweep(masterPath, opts) {
|
|
64
|
+
const result = { ingested: 0, skipped: 0, quarantined: 0, errors: [] };
|
|
65
|
+
const { config: mc } = ensureMachineConfig();
|
|
66
|
+
const lockPath = path.join(getGnosysHome(), INGEST_LOCK_NAME);
|
|
67
|
+
let release = null;
|
|
68
|
+
try {
|
|
69
|
+
release = acquireWriteLockSync(lockPath, "master-ingest");
|
|
70
|
+
touchMasterMarkerHeartbeat(masterPath);
|
|
71
|
+
assertMasterLeaseHeld(masterPath, mc.machineId);
|
|
72
|
+
const marker = readMasterMarker(masterPath);
|
|
73
|
+
if (!marker) {
|
|
74
|
+
result.errors.push("master.json missing");
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
const masterDb = new GnosysDB(masterPath);
|
|
78
|
+
if (!masterDb.isAvailable()) {
|
|
79
|
+
result.errors.push("master database unavailable");
|
|
80
|
+
masterDb.close();
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
for (const machineId of listMachineIds(masterPath)) {
|
|
85
|
+
quarantineStaleTmpFiles(masterPath, machineId);
|
|
86
|
+
const queue = listPendingStagingQueue(masterPath, machineId, masterDb);
|
|
87
|
+
for (const entry of queue) {
|
|
88
|
+
observeStagingFile(masterDb, machineId, entry.fileName, entry.memoryUlid);
|
|
89
|
+
const parsed = parseStagedFile(entry.filePath);
|
|
90
|
+
if (!parsed.ok) {
|
|
91
|
+
if (parsed.quarantine) {
|
|
92
|
+
quarantineStagingFile(entry.filePath, masterPath, machineId);
|
|
93
|
+
result.quarantined++;
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
result.errors.push(parsed.reason);
|
|
97
|
+
}
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (masterDb.isUlidProcessed(parsed.payload.id)) {
|
|
101
|
+
try {
|
|
102
|
+
unlinkSync(entry.filePath);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
// ignore
|
|
106
|
+
}
|
|
107
|
+
writeIngestReceipt(masterPath, machineId, parsed.payload.id, "deduped");
|
|
108
|
+
result.skipped++;
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
validateLeaseEpochBeforeWrite(masterPath, marker.epoch, mc.machineId);
|
|
113
|
+
const now = new Date().toISOString();
|
|
114
|
+
masterDb.insertMemory(payloadToMemory(parsed.payload, now));
|
|
115
|
+
if (!verifyMemoryExistsInDb(masterDb, parsed.payload.id)) {
|
|
116
|
+
result.errors.push(`read-back failed for ${parsed.payload.id}`);
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
masterDb.markUlidProcessed(parsed.payload.id, marker.epoch);
|
|
120
|
+
masterDb.recordStagingLedgerEntry({
|
|
121
|
+
stagingKey: `${machineId}/${entry.fileName}`,
|
|
122
|
+
machineId,
|
|
123
|
+
memoryUlid: parsed.payload.id,
|
|
124
|
+
firstSeenAt: masterDb.getStagingLedgerFirstSeenAt(`${machineId}/${entry.fileName}`) ?? now,
|
|
125
|
+
ingestEpoch: marker.epoch,
|
|
126
|
+
status: "ingested",
|
|
127
|
+
});
|
|
128
|
+
unlinkSync(entry.filePath);
|
|
129
|
+
writeIngestReceipt(masterPath, machineId, parsed.payload.id, "ingested");
|
|
130
|
+
result.ingested++;
|
|
131
|
+
}
|
|
132
|
+
catch (err) {
|
|
133
|
+
result.errors.push(err instanceof Error ? err.message : String(err));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
finally {
|
|
139
|
+
masterDb.close();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
result.errors.push(err instanceof Error ? err.message : String(err));
|
|
144
|
+
}
|
|
145
|
+
finally {
|
|
146
|
+
release?.();
|
|
147
|
+
}
|
|
148
|
+
if (opts?.json) {
|
|
149
|
+
console.log(JSON.stringify(result));
|
|
150
|
+
}
|
|
151
|
+
return result;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* v13 completion (5.12.x): sweep, then publish a fresh immutable snapshot
|
|
155
|
+
* when the sweep changed the DB (or none has ever been published). Clients
|
|
156
|
+
* read the published snapshot via a verified local copy instead of opening
|
|
157
|
+
* the live gnosys.db over the network — the hazard the design forbids.
|
|
158
|
+
*
|
|
159
|
+
* Kept separate from runMasterIngestSweep: publishMasterSnapshot acquires
|
|
160
|
+
* the same master-ingest lock, so it must run after the sweep releases it,
|
|
161
|
+
* and the sweep itself stays synchronous for existing callers.
|
|
162
|
+
*/
|
|
163
|
+
export async function runMasterIngestSweepAndPublish(masterPath, opts) {
|
|
164
|
+
const result = runMasterIngestSweep(masterPath, opts);
|
|
165
|
+
try {
|
|
166
|
+
const { getMasterManifest, publishMasterSnapshot } = await import("./syncSnapshot.js");
|
|
167
|
+
if (result.ingested > 0 || !getMasterManifest(masterPath)) {
|
|
168
|
+
await publishMasterSnapshot(masterPath);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
result.errors.push(`snapshot publish: ${err instanceof Error ? err.message : String(err)}`);
|
|
173
|
+
}
|
|
174
|
+
return result;
|
|
175
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Build launchd plist XML (testable without writing to disk). */
|
|
2
|
+
export declare function buildSyncIngestLaunchAgentPlist(intervalMinutes: number): string;
|
|
3
|
+
export declare function installSyncIngestLaunchAgent(intervalMinutes?: number): string | null;
|
|
4
|
+
export declare function uninstallSyncIngestLaunchAgent(): string | null;
|
|
5
|
+
export declare function getSyncIngestLaunchAgentStatus(): {
|
|
6
|
+
installed: boolean;
|
|
7
|
+
path: string;
|
|
8
|
+
};
|