@hasna/cloud 0.1.24 → 0.1.26

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.
@@ -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
- * Set up auto-sync hooks for a service's MCP server.
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
@@ -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;AAQ9C,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,SAAS,CAAC;IAClB,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;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,cAAc,CAmBlD;AAwFD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CAC3B,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,GAAG,EACX,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,MAAM,EAAE,GACf;IACD,WAAW,EAAE,MAAM,cAAc,GAAG,IAAI,CAAC;IACzC,UAAU,EAAE,MAAM,cAAc,GAAG,IAAI,CAAC;IACxC,MAAM,EAAE,cAAc,CAAC;CACxB,CAiDA;AAMD;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAC5B,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,GAAG,EACd,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,MAAM,EAAE,GACf,IAAI,CAEN"}
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/cli/index.js CHANGED
@@ -12810,8 +12810,21 @@ function logSync(direction, service, rows, errors2) {
12810
12810
  } catch {}
12811
12811
  }
12812
12812
  program2.name("cloud").description("Shared cloud infrastructure \u2014 database adapter, sync engine, feedback, dotfile migration").version("0.1.8");
12813
- program2.command("setup").description("Configure cloud settings").option("--host <host>", "RDS hostname").option("--port <port>", "RDS port", "5432").option("--username <user>", "RDS username").option("--password-env <env>", "Env var for RDS password", "HASNA_RDS_PASSWORD").option("--ssl", "Enable SSL", true).option("--no-ssl", "Disable SSL").option("--mode <mode>", "Mode: local, cloud, or hybrid", "local").option("--sync-interval <minutes>", "Auto-sync interval in minutes", "0").action((opts) => {
12813
+ program2.command("setup").description("Configure cloud settings \u2014 interactive wizard or flags").option("--host <host>", "RDS hostname").option("--port <port>", "RDS port", "5432").option("--username <user>", "RDS username").option("--password-env <env>", "Env var for RDS password", "HASNA_RDS_PASSWORD").option("--ssl", "Enable SSL", true).option("--no-ssl", "Disable SSL").option("--mode <mode>", "Mode: local, cloud, or hybrid").option("--schedule <interval>", "Sync schedule (e.g. 30m, 1h)").option("--migrate", "Run PG migrations after setup").option("--pull", "Pull data from cloud after setup").action(async (opts) => {
12814
12814
  const config = getCloudConfig();
12815
+ const isAutoDetect = !opts.host && !opts.username;
12816
+ if (isAutoDetect) {
12817
+ const envHost = process.env.HASNA_RDS_HOST;
12818
+ const envUser = process.env.HASNA_RDS_USERNAME;
12819
+ if (envHost && !config.rds.host) {
12820
+ config.rds.host = envHost;
12821
+ console.log(`Auto-detected RDS host: ${envHost}`);
12822
+ }
12823
+ if (envUser && !config.rds.username) {
12824
+ config.rds.username = envUser;
12825
+ console.log(`Auto-detected RDS username: ${envUser}`);
12826
+ }
12827
+ }
12815
12828
  if (opts.host)
12816
12829
  config.rds.host = opts.host;
12817
12830
  if (opts.port)
@@ -12821,13 +12834,86 @@ program2.command("setup").description("Configure cloud settings").option("--host
12821
12834
  if (opts.passwordEnv)
12822
12835
  config.rds.password_env = opts.passwordEnv;
12823
12836
  config.rds.ssl = opts.ssl;
12824
- if (opts.mode)
12837
+ if (opts.mode) {
12825
12838
  config.mode = opts.mode;
12826
- if (opts.syncInterval)
12827
- config.auto_sync_interval_minutes = parseInt(opts.syncInterval, 10);
12839
+ } else if (config.mode === "local" && config.rds.host) {
12840
+ config.mode = "hybrid";
12841
+ console.log("Mode set to: hybrid (auto-upgraded from local)");
12842
+ }
12828
12843
  saveCloudConfig(config);
12829
- console.log("Cloud configuration saved.");
12830
- console.log(JSON.stringify(config, null, 2));
12844
+ console.log(`
12845
+ \u2713 Configuration saved
12846
+ `);
12847
+ const password = process.env[config.rds.password_env];
12848
+ if (!password) {
12849
+ console.error(`\u2717 ${config.rds.password_env} not set in environment`);
12850
+ console.error(` Add it to ~/.secrets/hasna/rds/live.env and source it`);
12851
+ return;
12852
+ }
12853
+ if (config.rds.host) {
12854
+ process.stdout.write("Testing PG connection... ");
12855
+ try {
12856
+ const connStr = getConnectionString("postgres");
12857
+ const pg2 = new PgAdapterAsync2(connStr);
12858
+ await pg2.all("SELECT 1");
12859
+ await pg2.close();
12860
+ console.log(`\u2713 Connected
12861
+ `);
12862
+ } catch (err) {
12863
+ console.log(`\u2717 Failed: ${err?.message ?? String(err)}`);
12864
+ return;
12865
+ }
12866
+ if (opts.migrate !== false) {
12867
+ console.log("Creating databases & running migrations...");
12868
+ const dbResults = await ensureAllPgDatabases();
12869
+ const created = dbResults.filter((r) => r.created);
12870
+ if (created.length > 0) {
12871
+ console.log(` Created ${created.length} database(s): ${created.map((r) => r.service).join(", ")}`);
12872
+ }
12873
+ const migResults = await migrateAllServices();
12874
+ const applied = migResults.filter((r) => r.applied.length > 0);
12875
+ const totalApplied = migResults.reduce((s, r) => s + r.applied.length, 0);
12876
+ if (totalApplied > 0) {
12877
+ console.log(` Applied ${totalApplied} migration(s) across ${applied.length} service(s)`);
12878
+ } else {
12879
+ console.log(" All migrations up to date");
12880
+ }
12881
+ console.log("");
12882
+ }
12883
+ if (opts.schedule) {
12884
+ try {
12885
+ const minutes = parseInterval(opts.schedule);
12886
+ await registerSyncSchedule(minutes);
12887
+ console.log(`\u2713 Sync scheduled every ${minutes}m
12888
+ `);
12889
+ } catch (err) {
12890
+ console.error(`\u2717 Schedule failed: ${err?.message}`);
12891
+ }
12892
+ }
12893
+ if (opts.pull) {
12894
+ console.log("Pulling data from cloud...");
12895
+ const services = discoverServices();
12896
+ for (const service of services) {
12897
+ try {
12898
+ const dbPath = getDbPath2(service);
12899
+ const local = new SqliteAdapter2(dbPath);
12900
+ const connStr = getConnectionString(service);
12901
+ const cloud = new PgAdapterAsync2(connStr);
12902
+ const tables = (await listPgTables(cloud)).filter((t) => !isSyncExcludedTable(t));
12903
+ if (tables.length > 0) {
12904
+ const results = await syncPull(cloud, local, { tables });
12905
+ const written = results.reduce((s, r) => s + r.rowsWritten, 0);
12906
+ if (written > 0)
12907
+ console.log(` ${service}: ${written} rows`);
12908
+ }
12909
+ local.close();
12910
+ await cloud.close();
12911
+ } catch {}
12912
+ }
12913
+ console.log("");
12914
+ }
12915
+ }
12916
+ console.log("Setup complete. Run `cloud doctor` to verify everything.");
12831
12917
  });
12832
12918
  program2.command("status").description("Show current cloud configuration and connection health").action(async () => {
12833
12919
  const config = getCloudConfig();
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: () => discoverSyncableServices2,
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 readdirSync3, existsSync as existsSync6 } from "fs";
9364
- import { join as join6 } from "path";
9365
- import { homedir as homedir5 } from "os";
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 = join6(homedir5(), ".hasna");
9371
- if (!existsSync6(dataDir))
9370
+ const dataDir = join3(homedir3(), ".hasna");
9371
+ if (!existsSync3(dataDir))
9372
9372
  return [];
9373
9373
  try {
9374
- const entries = readdirSync3(dataDir, { withFileTypes: true });
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 discoverSyncableServices2() {
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 = join6(homedir5(), ".hasna", service);
9393
- if (!existsSync6(dataDir))
9392
+ const dataDir = join3(homedir3(), ".hasna", service);
9393
+ if (!existsSync3(dataDir))
9394
9394
  return null;
9395
9395
  const candidates = [
9396
- join6(dataDir, `${service}.db`),
9397
- join6(dataDir, "data.db"),
9398
- join6(dataDir, "database.db")
9396
+ join3(dataDir, `${service}.db`),
9397
+ join3(dataDir, "data.db"),
9398
+ join3(dataDir, "database.db")
9399
9399
  ];
9400
9400
  try {
9401
- const files = readdirSync3(dataDir);
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(join6(dataDir, f));
9404
+ candidates.push(join3(dataDir, f));
9405
9405
  }
9406
9406
  }
9407
9407
  } catch {}
9408
9408
  for (const p of candidates) {
9409
- if (existsSync6(p))
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 existsSync3, readFileSync as readFileSync2 } from "fs";
10408
- import { homedir as homedir3 } from "os";
10409
- import { join as join3 } from "path";
10410
- var AUTO_SYNC_CONFIG_PATH = join3(homedir3(), ".hasna", "cloud", "config.json");
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 (!existsSync3(AUTO_SYNC_CONFIG_PATH)) {
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, local, remote, tables) {
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 stats = direction === "pull" ? incrementalSyncPull(remote, local, tables) : incrementalSyncPush(local, remote, tables);
10441
- for (const s of stats) {
10442
- if (s.errors.length === 0) {
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.total_rows_synced += s.synced_rows;
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", local, remote, tables);
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", local, remote, tables);
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 existsSync4, readdirSync as readdirSync2 } from "fs";
10528
- import { join as join4 } from "path";
10538
+ import { existsSync as existsSync5, readdirSync as readdirSync3 } from "fs";
10539
+ import { join as join5 } from "path";
10529
10540
  init_dotfile();
10530
- function discoverSyncableServices() {
10541
+ function discoverSyncableServices2() {
10531
10542
  const hasnaDir = getHasnaDir();
10532
10543
  const services = [];
10533
10544
  try {
10534
- const entries = readdirSync2(hasnaDir, { withFileTypes: true });
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 = join4(hasnaDir, entry.name, `${entry.name}.db`);
10539
- if (existsSync4(dbPath)) {
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 = discoverSyncableServices();
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 = join4(getDataDir(service), `${service}.db`);
10562
- if (!existsSync4(dbPath)) {
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 join5, dirname } from "path";
10606
- import { existsSync as existsSync5, writeFileSync as writeFileSync2, unlinkSync, mkdirSync as mkdirSync3 } from "fs";
10607
- import { homedir as homedir4, platform } from "os";
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 = join5(homedir4(), ".hasna", "cloud");
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 = join5(dir, "scheduled-sync.ts");
10651
- const jsPath = join5(dir, "scheduled-sync.js");
10661
+ const tsPath = join6(dir, "scheduled-sync.ts");
10662
+ const jsPath = join6(dir, "scheduled-sync.js");
10652
10663
  try {
10653
- if (existsSync5(tsPath))
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
- join5(homedir4(), ".bun", "bin", "bun"),
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 (existsSync5(p))
10676
+ if (existsSync6(p))
10666
10677
  return p;
10667
10678
  }
10668
10679
  return "bun";
10669
10680
  }
10670
10681
  function getLaunchdPlistPath() {
10671
- return join5(homedir4(), "Library", "LaunchAgents", `com.hasna.cloud-sync.plist`);
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 = join5(CONFIG_DIR2, "sync.log");
10677
- const errorLogPath = join5(CONFIG_DIR2, "sync-error.log");
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>${homedir4()}</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 join5(homedir4(), ".config", "systemd", "user");
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=${homedir4()}
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(join5(dir, `${SERVICE_NAME}.service`), createSystemdService());
10764
- writeFileSync2(join5(dir, `${SERVICE_NAME}.timer`), createSystemdTimer(intervalMinutes));
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(join5(dir, `${SERVICE_NAME}.service`));
10785
+ unlinkSync(join6(dir, `${SERVICE_NAME}.service`));
10775
10786
  } catch {}
10776
10787
  try {
10777
- unlinkSync(join5(dir, `${SERVICE_NAME}.timer`));
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 = existsSync5(getLaunchdPlistPath()) ? "launchd" : "none";
10825
+ mechanism = existsSync6(getLaunchdPlistPath()) ? "launchd" : "none";
10815
10826
  } else {
10816
- mechanism = existsSync5(join5(getSystemdDir(), `${SERVICE_NAME}.timer`)) ? "systemd" : "none";
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
- discoverSyncableServices2 as discoverSyncableServicesV2,
11203
- discoverSyncableServices,
11213
+ discoverSyncableServices as discoverSyncableServicesV2,
11214
+ discoverSyncableServices2 as discoverSyncableServices,
11204
11215
  discoverServices,
11205
11216
  detectConflicts,
11206
11217
  createDatabase,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/cloud",
3
- "version": "0.1.24",
3
+ "version": "0.1.26",
4
4
  "description": "Shared cloud infrastructure — database adapter (SQLite + PostgreSQL), sync engine, feedback system, unified dotfile config",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",