@hasna/cloud 0.1.25 → 0.1.27
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/auto-sync.d.ts +5 -34
- package/dist/auto-sync.d.ts.map +1 -1
- package/dist/index.js +95 -84
- package/dist/mcp/index.js +34 -0
- package/package.json +1 -1
package/dist/auto-sync.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { DbAdapter } from "./adapter.js";
|
|
2
|
+
import { PgAdapterAsync } from "./adapter.js";
|
|
2
3
|
export interface AutoSyncConfig {
|
|
3
4
|
auto_sync_on_start: boolean;
|
|
4
5
|
auto_sync_on_stop: boolean;
|
|
@@ -6,7 +7,6 @@ export interface AutoSyncConfig {
|
|
|
6
7
|
export interface AutoSyncContext {
|
|
7
8
|
serviceName: string;
|
|
8
9
|
local: DbAdapter;
|
|
9
|
-
remote: DbAdapter;
|
|
10
10
|
tables: string[];
|
|
11
11
|
config: AutoSyncConfig;
|
|
12
12
|
}
|
|
@@ -18,40 +18,11 @@ export interface AutoSyncResult {
|
|
|
18
18
|
total_rows_synced: number;
|
|
19
19
|
errors: string[];
|
|
20
20
|
}
|
|
21
|
-
/**
|
|
22
|
-
* Read auto-sync configuration from `~/.hasna/cloud/config.json`.
|
|
23
|
-
* Falls back to defaults if the file does not exist or is malformed.
|
|
24
|
-
*/
|
|
25
21
|
export declare function getAutoSyncConfig(): AutoSyncConfig;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
* - On connect: if `auto_sync_on_start` and mode is `hybrid` or `cloud`,
|
|
30
|
-
* pull from cloud to local.
|
|
31
|
-
* - On disconnect/SIGTERM: if `auto_sync_on_stop` and mode is `hybrid` or `cloud`,
|
|
32
|
-
* push from local to cloud.
|
|
33
|
-
*
|
|
34
|
-
* @param serviceName - The service identifier.
|
|
35
|
-
* @param server - The MCP server instance (any object with `onconnect`/`ondisconnect` events).
|
|
36
|
-
* @param local - The local database adapter.
|
|
37
|
-
* @param remote - The remote database adapter.
|
|
38
|
-
* @param tables - Tables to sync.
|
|
39
|
-
* @returns An object with methods to manually trigger start/stop syncs.
|
|
40
|
-
*/
|
|
41
|
-
export declare function setupAutoSync(serviceName: string, server: any, local: DbAdapter, remote: DbAdapter, tables: string[]): {
|
|
42
|
-
syncOnStart: () => AutoSyncResult | null;
|
|
43
|
-
syncOnStop: () => AutoSyncResult | null;
|
|
22
|
+
export declare function setupAutoSync(serviceName: string, server: any, local: DbAdapter, remote: DbAdapter | PgAdapterAsync, tables: string[]): {
|
|
23
|
+
syncOnStart: () => Promise<AutoSyncResult | null>;
|
|
24
|
+
syncOnStop: () => Promise<AutoSyncResult | null>;
|
|
44
25
|
config: AutoSyncConfig;
|
|
45
26
|
};
|
|
46
|
-
|
|
47
|
-
* Enable auto-sync for a service. Simplified entry point that services
|
|
48
|
-
* can call with minimal configuration.
|
|
49
|
-
*
|
|
50
|
-
* @param serviceName - The service name (used for logging context).
|
|
51
|
-
* @param mcpServer - The MCP server instance.
|
|
52
|
-
* @param local - The local database adapter.
|
|
53
|
-
* @param remote - The remote database adapter.
|
|
54
|
-
* @param tables - Tables to sync on start/stop.
|
|
55
|
-
*/
|
|
56
|
-
export declare function enableAutoSync(serviceName: string, mcpServer: any, local: DbAdapter, remote: DbAdapter, tables: string[]): void;
|
|
27
|
+
export declare function enableAutoSync(serviceName: string, mcpServer: any, local: DbAdapter, remote: DbAdapter | PgAdapterAsync, tables: string[]): void;
|
|
57
28
|
//# sourceMappingURL=auto-sync.d.ts.map
|
package/dist/auto-sync.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auto-sync.d.ts","sourceRoot":"","sources":["../src/auto-sync.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"auto-sync.d.ts","sourceRoot":"","sources":["../src/auto-sync.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAS9C,MAAM,WAAW,cAAc;IAC7B,kBAAkB,EAAE,OAAO,CAAC;IAC5B,iBAAiB,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,SAAS,CAAC;IACjB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,MAAM,EAAE,cAAc,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC;IACxB,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAaD,wBAAgB,iBAAiB,IAAI,cAAc,CAmBlD;AAiGD,wBAAgB,aAAa,CAC3B,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,GAAG,EACX,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,SAAS,GAAG,cAAc,EAClC,MAAM,EAAE,MAAM,EAAE,GACf;IACD,WAAW,EAAE,MAAM,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAClD,UAAU,EAAE,MAAM,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IACjD,MAAM,EAAE,cAAc,CAAC;CACxB,CA0CA;AAMD,wBAAgB,cAAc,CAC5B,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,GAAG,EACd,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,SAAS,GAAG,cAAc,EAClC,MAAM,EAAE,MAAM,EAAE,GACf,IAAI,CAEN"}
|
package/dist/index.js
CHANGED
|
@@ -9355,23 +9355,23 @@ var exports_discover = {};
|
|
|
9355
9355
|
__export(exports_discover, {
|
|
9356
9356
|
isSyncExcludedTable: () => isSyncExcludedTable,
|
|
9357
9357
|
getServiceDbPath: () => getServiceDbPath,
|
|
9358
|
-
discoverSyncableServices: () =>
|
|
9358
|
+
discoverSyncableServices: () => discoverSyncableServices,
|
|
9359
9359
|
discoverServices: () => discoverServices,
|
|
9360
9360
|
SYNC_EXCLUDED_TABLE_PATTERNS: () => SYNC_EXCLUDED_TABLE_PATTERNS,
|
|
9361
9361
|
KNOWN_PG_SERVICES: () => KNOWN_PG_SERVICES
|
|
9362
9362
|
});
|
|
9363
|
-
import { readdirSync as
|
|
9364
|
-
import { join as
|
|
9365
|
-
import { homedir as
|
|
9363
|
+
import { readdirSync as readdirSync2, existsSync as existsSync3 } from "fs";
|
|
9364
|
+
import { join as join3 } from "path";
|
|
9365
|
+
import { homedir as homedir3 } from "os";
|
|
9366
9366
|
function isSyncExcludedTable(table) {
|
|
9367
9367
|
return SYNC_EXCLUDED_TABLE_PATTERNS.some((p) => p.test(table));
|
|
9368
9368
|
}
|
|
9369
9369
|
function discoverServices() {
|
|
9370
|
-
const dataDir =
|
|
9371
|
-
if (!
|
|
9370
|
+
const dataDir = join3(homedir3(), ".hasna");
|
|
9371
|
+
if (!existsSync3(dataDir))
|
|
9372
9372
|
return [];
|
|
9373
9373
|
try {
|
|
9374
|
-
const entries =
|
|
9374
|
+
const entries = readdirSync2(dataDir, { withFileTypes: true });
|
|
9375
9375
|
return entries.filter((e) => {
|
|
9376
9376
|
if (!e.isDirectory())
|
|
9377
9377
|
return false;
|
|
@@ -9383,30 +9383,30 @@ function discoverServices() {
|
|
|
9383
9383
|
return [];
|
|
9384
9384
|
}
|
|
9385
9385
|
}
|
|
9386
|
-
function
|
|
9386
|
+
function discoverSyncableServices() {
|
|
9387
9387
|
const local = discoverServices();
|
|
9388
9388
|
const pgSet = new Set(KNOWN_PG_SERVICES);
|
|
9389
9389
|
return local.filter((s) => pgSet.has(s));
|
|
9390
9390
|
}
|
|
9391
9391
|
function getServiceDbPath(service) {
|
|
9392
|
-
const dataDir =
|
|
9393
|
-
if (!
|
|
9392
|
+
const dataDir = join3(homedir3(), ".hasna", service);
|
|
9393
|
+
if (!existsSync3(dataDir))
|
|
9394
9394
|
return null;
|
|
9395
9395
|
const candidates = [
|
|
9396
|
-
|
|
9397
|
-
|
|
9398
|
-
|
|
9396
|
+
join3(dataDir, `${service}.db`),
|
|
9397
|
+
join3(dataDir, "data.db"),
|
|
9398
|
+
join3(dataDir, "database.db")
|
|
9399
9399
|
];
|
|
9400
9400
|
try {
|
|
9401
|
-
const files =
|
|
9401
|
+
const files = readdirSync2(dataDir);
|
|
9402
9402
|
for (const f of files) {
|
|
9403
9403
|
if (f.endsWith(".db") && !f.endsWith("-wal") && !f.endsWith("-shm")) {
|
|
9404
|
-
candidates.push(
|
|
9404
|
+
candidates.push(join3(dataDir, f));
|
|
9405
9405
|
}
|
|
9406
9406
|
}
|
|
9407
9407
|
} catch {}
|
|
9408
9408
|
for (const p of candidates) {
|
|
9409
|
-
if (
|
|
9409
|
+
if (existsSync3(p))
|
|
9410
9410
|
return p;
|
|
9411
9411
|
}
|
|
9412
9412
|
return null;
|
|
@@ -10403,18 +10403,20 @@ function resetAllSyncMeta(db) {
|
|
|
10403
10403
|
db.run(`DELETE FROM _sync_meta`);
|
|
10404
10404
|
}
|
|
10405
10405
|
// src/auto-sync.ts
|
|
10406
|
+
init_adapter();
|
|
10406
10407
|
init_config();
|
|
10407
|
-
import { existsSync as
|
|
10408
|
-
import { homedir as
|
|
10409
|
-
import { join as
|
|
10410
|
-
|
|
10408
|
+
import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
|
|
10409
|
+
import { homedir as homedir4 } from "os";
|
|
10410
|
+
import { join as join4 } from "path";
|
|
10411
|
+
init_discover();
|
|
10412
|
+
var AUTO_SYNC_CONFIG_PATH = join4(homedir4(), ".hasna", "cloud", "config.json");
|
|
10411
10413
|
var DEFAULT_AUTO_SYNC_CONFIG = {
|
|
10412
10414
|
auto_sync_on_start: true,
|
|
10413
10415
|
auto_sync_on_stop: true
|
|
10414
10416
|
};
|
|
10415
10417
|
function getAutoSyncConfig() {
|
|
10416
10418
|
try {
|
|
10417
|
-
if (!
|
|
10419
|
+
if (!existsSync4(AUTO_SYNC_CONFIG_PATH)) {
|
|
10418
10420
|
return { ...DEFAULT_AUTO_SYNC_CONFIG };
|
|
10419
10421
|
}
|
|
10420
10422
|
const raw = JSON.parse(readFileSync2(AUTO_SYNC_CONFIG_PATH, "utf-8"));
|
|
@@ -10426,7 +10428,7 @@ function getAutoSyncConfig() {
|
|
|
10426
10428
|
return { ...DEFAULT_AUTO_SYNC_CONFIG };
|
|
10427
10429
|
}
|
|
10428
10430
|
}
|
|
10429
|
-
function executeAutoSync(event,
|
|
10431
|
+
async function executeAutoSync(event, serviceName, local, tables) {
|
|
10430
10432
|
const direction = event === "start" ? "pull" : "push";
|
|
10431
10433
|
const result = {
|
|
10432
10434
|
event,
|
|
@@ -10436,18 +10438,31 @@ function executeAutoSync(event, local, remote, tables) {
|
|
|
10436
10438
|
total_rows_synced: 0,
|
|
10437
10439
|
errors: []
|
|
10438
10440
|
};
|
|
10441
|
+
let remote = null;
|
|
10439
10442
|
try {
|
|
10440
|
-
const
|
|
10441
|
-
|
|
10442
|
-
|
|
10443
|
+
const connStr = getConnectionString(serviceName);
|
|
10444
|
+
remote = new PgAdapterAsync(connStr);
|
|
10445
|
+
const syncTables = tables.length > 0 ? tables.filter((t) => !isSyncExcludedTable(t)) : direction === "push" ? listSqliteTables(local).filter((t) => !isSyncExcludedTable(t)) : (await listPgTables(remote)).filter((t) => !isSyncExcludedTable(t));
|
|
10446
|
+
if (syncTables.length === 0) {
|
|
10447
|
+
result.success = true;
|
|
10448
|
+
return result;
|
|
10449
|
+
}
|
|
10450
|
+
const results = direction === "pull" ? await syncPull(remote, local, { tables: syncTables }) : await syncPush(local, remote, { tables: syncTables });
|
|
10451
|
+
for (const r of results) {
|
|
10452
|
+
if (r.errors.length === 0)
|
|
10443
10453
|
result.tables_synced++;
|
|
10444
|
-
|
|
10445
|
-
result.
|
|
10446
|
-
result.errors.push(...s.errors);
|
|
10454
|
+
result.total_rows_synced += r.rowsWritten;
|
|
10455
|
+
result.errors.push(...r.errors);
|
|
10447
10456
|
}
|
|
10448
10457
|
result.success = result.errors.length === 0;
|
|
10449
10458
|
} catch (err) {
|
|
10450
10459
|
result.errors.push(err?.message ?? String(err));
|
|
10460
|
+
} finally {
|
|
10461
|
+
if (remote) {
|
|
10462
|
+
try {
|
|
10463
|
+
await remote.close();
|
|
10464
|
+
} catch {}
|
|
10465
|
+
}
|
|
10451
10466
|
}
|
|
10452
10467
|
return result;
|
|
10453
10468
|
}
|
|
@@ -10457,64 +10472,60 @@ function installSignalHandlers() {
|
|
|
10457
10472
|
if (signalHandlersInstalled)
|
|
10458
10473
|
return;
|
|
10459
10474
|
signalHandlersInstalled = true;
|
|
10460
|
-
const handleExit = () => {
|
|
10475
|
+
const handleExit = async () => {
|
|
10461
10476
|
for (const fn of cleanupHandlers) {
|
|
10462
10477
|
try {
|
|
10463
|
-
fn();
|
|
10478
|
+
await fn();
|
|
10464
10479
|
} catch {}
|
|
10465
10480
|
}
|
|
10466
10481
|
};
|
|
10467
|
-
process.on("SIGTERM", () => {
|
|
10468
|
-
handleExit();
|
|
10482
|
+
process.on("SIGTERM", async () => {
|
|
10483
|
+
await handleExit();
|
|
10469
10484
|
process.exit(0);
|
|
10470
10485
|
});
|
|
10471
|
-
process.on("SIGINT", () => {
|
|
10472
|
-
handleExit();
|
|
10486
|
+
process.on("SIGINT", async () => {
|
|
10487
|
+
await handleExit();
|
|
10473
10488
|
process.exit(0);
|
|
10474
10489
|
});
|
|
10475
|
-
process.on("beforeExit", () => {
|
|
10476
|
-
handleExit();
|
|
10490
|
+
process.on("beforeExit", async () => {
|
|
10491
|
+
await handleExit();
|
|
10477
10492
|
});
|
|
10478
10493
|
}
|
|
10479
10494
|
function setupAutoSync(serviceName, server, local, remote, tables) {
|
|
10480
10495
|
const config = getAutoSyncConfig();
|
|
10481
10496
|
const cloudConfig = getCloudConfig();
|
|
10482
10497
|
const isSyncEnabled = cloudConfig.mode === "hybrid" || cloudConfig.mode === "cloud";
|
|
10483
|
-
const syncOnStart = () => {
|
|
10498
|
+
const syncOnStart = async () => {
|
|
10484
10499
|
if (!config.auto_sync_on_start || !isSyncEnabled)
|
|
10485
10500
|
return null;
|
|
10486
|
-
return executeAutoSync("start",
|
|
10501
|
+
return executeAutoSync("start", serviceName, local, tables);
|
|
10487
10502
|
};
|
|
10488
|
-
const syncOnStop = () => {
|
|
10503
|
+
const syncOnStop = async () => {
|
|
10489
10504
|
if (!config.auto_sync_on_stop || !isSyncEnabled)
|
|
10490
10505
|
return null;
|
|
10491
|
-
return executeAutoSync("stop",
|
|
10506
|
+
return executeAutoSync("stop", serviceName, local, tables);
|
|
10492
10507
|
};
|
|
10493
10508
|
if (server && typeof server.onconnect === "function") {
|
|
10494
10509
|
const origOnConnect = server.onconnect;
|
|
10495
|
-
server.onconnect = (...args) => {
|
|
10496
|
-
syncOnStart();
|
|
10510
|
+
server.onconnect = async (...args) => {
|
|
10511
|
+
await syncOnStart();
|
|
10497
10512
|
return origOnConnect.apply(server, args);
|
|
10498
10513
|
};
|
|
10499
10514
|
} else if (server && typeof server.on === "function") {
|
|
10500
|
-
server.on("connect", () =>
|
|
10501
|
-
syncOnStart();
|
|
10502
|
-
});
|
|
10515
|
+
server.on("connect", () => syncOnStart());
|
|
10503
10516
|
}
|
|
10504
10517
|
if (server && typeof server.ondisconnect === "function") {
|
|
10505
10518
|
const origOnDisconnect = server.ondisconnect;
|
|
10506
|
-
server.ondisconnect = (...args) => {
|
|
10507
|
-
syncOnStop();
|
|
10519
|
+
server.ondisconnect = async (...args) => {
|
|
10520
|
+
await syncOnStop();
|
|
10508
10521
|
return origOnDisconnect.apply(server, args);
|
|
10509
10522
|
};
|
|
10510
10523
|
} else if (server && typeof server.on === "function") {
|
|
10511
|
-
server.on("disconnect", () =>
|
|
10512
|
-
syncOnStop();
|
|
10513
|
-
});
|
|
10524
|
+
server.on("disconnect", () => syncOnStop());
|
|
10514
10525
|
}
|
|
10515
10526
|
installSignalHandlers();
|
|
10516
|
-
cleanupHandlers.push(() => {
|
|
10517
|
-
syncOnStop();
|
|
10527
|
+
cleanupHandlers.push(async () => {
|
|
10528
|
+
await syncOnStop();
|
|
10518
10529
|
});
|
|
10519
10530
|
return { syncOnStart, syncOnStop, config };
|
|
10520
10531
|
}
|
|
@@ -10524,19 +10535,19 @@ function enableAutoSync(serviceName, mcpServer, local, remote, tables) {
|
|
|
10524
10535
|
// src/scheduled-sync.ts
|
|
10525
10536
|
init_config();
|
|
10526
10537
|
init_adapter();
|
|
10527
|
-
import { existsSync as
|
|
10528
|
-
import { join as
|
|
10538
|
+
import { existsSync as existsSync5, readdirSync as readdirSync3 } from "fs";
|
|
10539
|
+
import { join as join5 } from "path";
|
|
10529
10540
|
init_dotfile();
|
|
10530
|
-
function
|
|
10541
|
+
function discoverSyncableServices2() {
|
|
10531
10542
|
const hasnaDir = getHasnaDir();
|
|
10532
10543
|
const services = [];
|
|
10533
10544
|
try {
|
|
10534
|
-
const entries =
|
|
10545
|
+
const entries = readdirSync3(hasnaDir, { withFileTypes: true });
|
|
10535
10546
|
for (const entry of entries) {
|
|
10536
10547
|
if (!entry.isDirectory())
|
|
10537
10548
|
continue;
|
|
10538
|
-
const dbPath =
|
|
10539
|
-
if (
|
|
10549
|
+
const dbPath = join5(hasnaDir, entry.name, `${entry.name}.db`);
|
|
10550
|
+
if (existsSync5(dbPath)) {
|
|
10540
10551
|
services.push(entry.name);
|
|
10541
10552
|
}
|
|
10542
10553
|
}
|
|
@@ -10547,7 +10558,7 @@ async function runScheduledSync() {
|
|
|
10547
10558
|
const config = getCloudConfig();
|
|
10548
10559
|
if (config.mode === "local")
|
|
10549
10560
|
return [];
|
|
10550
|
-
const services =
|
|
10561
|
+
const services = discoverSyncableServices2();
|
|
10551
10562
|
const results = [];
|
|
10552
10563
|
let remote = null;
|
|
10553
10564
|
for (const service of services) {
|
|
@@ -10558,8 +10569,8 @@ async function runScheduledSync() {
|
|
|
10558
10569
|
errors: []
|
|
10559
10570
|
};
|
|
10560
10571
|
try {
|
|
10561
|
-
const dbPath =
|
|
10562
|
-
if (!
|
|
10572
|
+
const dbPath = join5(getDataDir(service), `${service}.db`);
|
|
10573
|
+
if (!existsSync5(dbPath)) {
|
|
10563
10574
|
continue;
|
|
10564
10575
|
}
|
|
10565
10576
|
const local = new SqliteAdapter(dbPath);
|
|
@@ -10602,11 +10613,11 @@ async function runScheduledSync() {
|
|
|
10602
10613
|
}
|
|
10603
10614
|
// src/sync-schedule.ts
|
|
10604
10615
|
init_config();
|
|
10605
|
-
import { join as
|
|
10606
|
-
import { existsSync as
|
|
10607
|
-
import { homedir as
|
|
10616
|
+
import { join as join6, dirname } from "path";
|
|
10617
|
+
import { existsSync as existsSync6, writeFileSync as writeFileSync2, unlinkSync, mkdirSync as mkdirSync3 } from "fs";
|
|
10618
|
+
import { homedir as homedir5, platform } from "os";
|
|
10608
10619
|
var SERVICE_NAME = "hasna-cloud-sync";
|
|
10609
|
-
var CONFIG_DIR2 =
|
|
10620
|
+
var CONFIG_DIR2 = join6(homedir5(), ".hasna", "cloud");
|
|
10610
10621
|
function parseInterval(input) {
|
|
10611
10622
|
const trimmed = input.trim().toLowerCase();
|
|
10612
10623
|
const hourMatch = trimmed.match(/^(\d+)\s*h$/);
|
|
@@ -10647,34 +10658,34 @@ function minutesToCron(minutes) {
|
|
|
10647
10658
|
}
|
|
10648
10659
|
function getWorkerPath() {
|
|
10649
10660
|
const dir = typeof import.meta.dir === "string" ? import.meta.dir : dirname(import.meta.url.replace("file://", ""));
|
|
10650
|
-
const tsPath =
|
|
10651
|
-
const jsPath =
|
|
10661
|
+
const tsPath = join6(dir, "scheduled-sync.ts");
|
|
10662
|
+
const jsPath = join6(dir, "scheduled-sync.js");
|
|
10652
10663
|
try {
|
|
10653
|
-
if (
|
|
10664
|
+
if (existsSync6(tsPath))
|
|
10654
10665
|
return tsPath;
|
|
10655
10666
|
} catch {}
|
|
10656
10667
|
return jsPath;
|
|
10657
10668
|
}
|
|
10658
10669
|
function getBunPath() {
|
|
10659
10670
|
const candidates = [
|
|
10660
|
-
|
|
10671
|
+
join6(homedir5(), ".bun", "bin", "bun"),
|
|
10661
10672
|
"/usr/local/bin/bun",
|
|
10662
10673
|
"/usr/bin/bun"
|
|
10663
10674
|
];
|
|
10664
10675
|
for (const p of candidates) {
|
|
10665
|
-
if (
|
|
10676
|
+
if (existsSync6(p))
|
|
10666
10677
|
return p;
|
|
10667
10678
|
}
|
|
10668
10679
|
return "bun";
|
|
10669
10680
|
}
|
|
10670
10681
|
function getLaunchdPlistPath() {
|
|
10671
|
-
return
|
|
10682
|
+
return join6(homedir5(), "Library", "LaunchAgents", `com.hasna.cloud-sync.plist`);
|
|
10672
10683
|
}
|
|
10673
10684
|
function createLaunchdPlist(intervalMinutes) {
|
|
10674
10685
|
const workerPath = getWorkerPath();
|
|
10675
10686
|
const bunPath = getBunPath();
|
|
10676
|
-
const logPath =
|
|
10677
|
-
const errorLogPath =
|
|
10687
|
+
const logPath = join6(CONFIG_DIR2, "sync.log");
|
|
10688
|
+
const errorLogPath = join6(CONFIG_DIR2, "sync-error.log");
|
|
10678
10689
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
10679
10690
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
10680
10691
|
<plist version="1.0">
|
|
@@ -10700,7 +10711,7 @@ function createLaunchdPlist(intervalMinutes) {
|
|
|
10700
10711
|
<key>PATH</key>
|
|
10701
10712
|
<string>${process.env.PATH || "/usr/local/bin:/usr/bin:/bin"}</string>
|
|
10702
10713
|
<key>HOME</key>
|
|
10703
|
-
<string>${
|
|
10714
|
+
<string>${homedir5()}</string>
|
|
10704
10715
|
</dict>
|
|
10705
10716
|
</dict>
|
|
10706
10717
|
</plist>`;
|
|
@@ -10725,7 +10736,7 @@ async function removeLaunchd() {
|
|
|
10725
10736
|
} catch {}
|
|
10726
10737
|
}
|
|
10727
10738
|
function getSystemdDir() {
|
|
10728
|
-
return
|
|
10739
|
+
return join6(homedir5(), ".config", "systemd", "user");
|
|
10729
10740
|
}
|
|
10730
10741
|
function createSystemdService() {
|
|
10731
10742
|
const workerPath = getWorkerPath();
|
|
@@ -10737,7 +10748,7 @@ After=network.target
|
|
|
10737
10748
|
[Service]
|
|
10738
10749
|
Type=oneshot
|
|
10739
10750
|
ExecStart=${bunPath} run ${workerPath}
|
|
10740
|
-
Environment=HOME=${
|
|
10751
|
+
Environment=HOME=${homedir5()}
|
|
10741
10752
|
Environment=PATH=${process.env.PATH || "/usr/local/bin:/usr/bin:/bin"}
|
|
10742
10753
|
|
|
10743
10754
|
[Install]
|
|
@@ -10760,8 +10771,8 @@ WantedBy=timers.target
|
|
|
10760
10771
|
async function registerSystemd(intervalMinutes) {
|
|
10761
10772
|
const dir = getSystemdDir();
|
|
10762
10773
|
mkdirSync3(dir, { recursive: true });
|
|
10763
|
-
writeFileSync2(
|
|
10764
|
-
writeFileSync2(
|
|
10774
|
+
writeFileSync2(join6(dir, `${SERVICE_NAME}.service`), createSystemdService());
|
|
10775
|
+
writeFileSync2(join6(dir, `${SERVICE_NAME}.timer`), createSystemdTimer(intervalMinutes));
|
|
10765
10776
|
await Bun.spawn(["systemctl", "--user", "daemon-reload"]).exited;
|
|
10766
10777
|
await Bun.spawn(["systemctl", "--user", "enable", "--now", `${SERVICE_NAME}.timer`]).exited;
|
|
10767
10778
|
}
|
|
@@ -10771,10 +10782,10 @@ async function removeSystemd() {
|
|
|
10771
10782
|
} catch {}
|
|
10772
10783
|
const dir = getSystemdDir();
|
|
10773
10784
|
try {
|
|
10774
|
-
unlinkSync(
|
|
10785
|
+
unlinkSync(join6(dir, `${SERVICE_NAME}.service`));
|
|
10775
10786
|
} catch {}
|
|
10776
10787
|
try {
|
|
10777
|
-
unlinkSync(
|
|
10788
|
+
unlinkSync(join6(dir, `${SERVICE_NAME}.timer`));
|
|
10778
10789
|
} catch {}
|
|
10779
10790
|
try {
|
|
10780
10791
|
await Bun.spawn(["systemctl", "--user", "daemon-reload"]).exited;
|
|
@@ -10811,9 +10822,9 @@ function getSyncScheduleStatus() {
|
|
|
10811
10822
|
let mechanism = "none";
|
|
10812
10823
|
if (registered) {
|
|
10813
10824
|
if (platform() === "darwin") {
|
|
10814
|
-
mechanism =
|
|
10825
|
+
mechanism = existsSync6(getLaunchdPlistPath()) ? "launchd" : "none";
|
|
10815
10826
|
} else {
|
|
10816
|
-
mechanism =
|
|
10827
|
+
mechanism = existsSync6(join6(getSystemdDir(), `${SERVICE_NAME}.timer`)) ? "systemd" : "none";
|
|
10817
10828
|
}
|
|
10818
10829
|
}
|
|
10819
10830
|
return {
|
|
@@ -11199,8 +11210,8 @@ export {
|
|
|
11199
11210
|
ensureConflictsTable,
|
|
11200
11211
|
ensureAllPgDatabases,
|
|
11201
11212
|
enableAutoSync,
|
|
11202
|
-
|
|
11203
|
-
discoverSyncableServices,
|
|
11213
|
+
discoverSyncableServices as discoverSyncableServicesV2,
|
|
11214
|
+
discoverSyncableServices2 as discoverSyncableServices,
|
|
11204
11215
|
discoverServices,
|
|
11205
11216
|
detectConflicts,
|
|
11206
11217
|
createDatabase,
|
package/dist/mcp/index.js
CHANGED
|
@@ -26059,6 +26059,40 @@ Done. ${results.length} services, ${totalApplied} migrations applied.`);
|
|
|
26059
26059
|
return { content: [{ type: "text", text: lines.join(`
|
|
26060
26060
|
`) }] };
|
|
26061
26061
|
});
|
|
26062
|
+
var mcpAgentRegistry = new Map;
|
|
26063
|
+
server.tool("register_agent", "Register an agent session for attribution", {
|
|
26064
|
+
name: exports_external.string().describe("Agent name"),
|
|
26065
|
+
session_id: exports_external.string().optional().describe("Session identifier")
|
|
26066
|
+
}, async ({ name, session_id }) => {
|
|
26067
|
+
const existing = [...mcpAgentRegistry.values()].find((a) => a.name === name);
|
|
26068
|
+
if (existing) {
|
|
26069
|
+
existing.last_seen_at = new Date().toISOString();
|
|
26070
|
+
return { content: [{ type: "text", text: JSON.stringify({ agent_id: existing.id, name: existing.name, last_seen_at: existing.last_seen_at }) }] };
|
|
26071
|
+
}
|
|
26072
|
+
const id = Math.random().toString(36).slice(2, 10);
|
|
26073
|
+
const agent = { id, name, last_seen_at: new Date().toISOString() };
|
|
26074
|
+
mcpAgentRegistry.set(id, agent);
|
|
26075
|
+
return { content: [{ type: "text", text: JSON.stringify(agent) }] };
|
|
26076
|
+
});
|
|
26077
|
+
server.tool("heartbeat", "Update agent last_seen_at", {
|
|
26078
|
+
agent_id: exports_external.string().optional().describe("Agent ID (optional \u2014 updates by name if registered)")
|
|
26079
|
+
}, async () => {
|
|
26080
|
+
return { content: [{ type: "text", text: `heartbeat at ${new Date().toISOString()}` }] };
|
|
26081
|
+
});
|
|
26082
|
+
server.tool("list_agents", "List registered agents", {}, async () => {
|
|
26083
|
+
const agents = [...mcpAgentRegistry.values()];
|
|
26084
|
+
return { content: [{ type: "text", text: agents.length > 0 ? JSON.stringify(agents) : "No agents registered" }] };
|
|
26085
|
+
});
|
|
26086
|
+
server.tool("set_focus", "Set active project context", {
|
|
26087
|
+
agent_id: exports_external.string().describe("Agent ID"),
|
|
26088
|
+
project_id: exports_external.string().optional().describe("Project to focus on")
|
|
26089
|
+
}, async ({ agent_id, project_id }) => {
|
|
26090
|
+
const agent = mcpAgentRegistry.get(agent_id);
|
|
26091
|
+
if (!agent)
|
|
26092
|
+
return { content: [{ type: "text", text: `Agent not found: ${agent_id}` }], isError: true };
|
|
26093
|
+
agent.project_id = project_id;
|
|
26094
|
+
return { content: [{ type: "text", text: project_id ? `Focus set: ${project_id}` : "Focus cleared" }] };
|
|
26095
|
+
});
|
|
26062
26096
|
async function main() {
|
|
26063
26097
|
const transport = new StdioServerTransport;
|
|
26064
26098
|
await server.connect(transport);
|
package/package.json
CHANGED