@hasna/cloud 0.1.17 → 0.1.18

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/index.js CHANGED
@@ -11343,15 +11343,15 @@ __export(exports_discover, {
11343
11343
  SYNC_EXCLUDED_TABLE_PATTERNS: () => SYNC_EXCLUDED_TABLE_PATTERNS2,
11344
11344
  KNOWN_PG_SERVICES: () => KNOWN_PG_SERVICES
11345
11345
  });
11346
- import { readdirSync as readdirSync5, existsSync as existsSync7 } from "fs";
11346
+ import { readdirSync as readdirSync5, existsSync as existsSync8 } from "fs";
11347
11347
  import { join as join8 } from "path";
11348
- import { homedir as homedir6 } from "os";
11348
+ import { homedir as homedir7 } from "os";
11349
11349
  function isSyncExcludedTable2(table) {
11350
11350
  return SYNC_EXCLUDED_TABLE_PATTERNS2.some((p) => p.test(table));
11351
11351
  }
11352
11352
  function discoverServices2() {
11353
- const dataDir = join8(homedir6(), ".hasna");
11354
- if (!existsSync7(dataDir))
11353
+ const dataDir = join8(homedir7(), ".hasna");
11354
+ if (!existsSync8(dataDir))
11355
11355
  return [];
11356
11356
  try {
11357
11357
  const entries = readdirSync5(dataDir, { withFileTypes: true });
@@ -11372,8 +11372,8 @@ function discoverSyncableServices2() {
11372
11372
  return local.filter((s) => pgSet.has(s));
11373
11373
  }
11374
11374
  function getServiceDbPath(service) {
11375
- const dataDir = join8(homedir6(), ".hasna", service);
11376
- if (!existsSync7(dataDir))
11375
+ const dataDir = join8(homedir7(), ".hasna", service);
11376
+ if (!existsSync8(dataDir))
11377
11377
  return null;
11378
11378
  const candidates = [
11379
11379
  join8(dataDir, `${service}.db`),
@@ -11389,7 +11389,7 @@ function getServiceDbPath(service) {
11389
11389
  }
11390
11390
  } catch {}
11391
11391
  for (const p of candidates) {
11392
- if (existsSync7(p))
11392
+ if (existsSync8(p))
11393
11393
  return p;
11394
11394
  }
11395
11395
  return null;
@@ -12131,18 +12131,10 @@ class PgAdapterAsync2 {
12131
12131
  // src/sync-schedule.ts
12132
12132
  init_config();
12133
12133
  import { join as join5, dirname } from "path";
12134
- var CRON_TITLE = "hasna-cloud-sync";
12135
- function getWorkerPath() {
12136
- const dir = typeof import.meta.dir === "string" ? import.meta.dir : dirname(import.meta.url.replace("file://", ""));
12137
- const tsPath = join5(dir, "scheduled-sync.ts");
12138
- const jsPath = join5(dir, "scheduled-sync.js");
12139
- try {
12140
- const { existsSync: existsSync5 } = __require("fs");
12141
- if (existsSync5(tsPath))
12142
- return tsPath;
12143
- } catch {}
12144
- return jsPath;
12145
- }
12134
+ import { existsSync as existsSync5, writeFileSync as writeFileSync3, unlinkSync, mkdirSync as mkdirSync5 } from "fs";
12135
+ import { homedir as homedir5, platform } from "os";
12136
+ var SERVICE_NAME = "hasna-cloud-sync";
12137
+ var CONFIG_DIR3 = join5(homedir5(), ".hasna", "cloud");
12146
12138
  function parseInterval(input) {
12147
12139
  const trimmed = input.trim().toLowerCase();
12148
12140
  const hourMatch = trimmed.match(/^(\d+)\s*h$/);
@@ -12181,19 +12173,161 @@ function minutesToCron(minutes) {
12181
12173
  }
12182
12174
  return `*/${minutes} * * * *`;
12183
12175
  }
12176
+ function getWorkerPath() {
12177
+ const dir = typeof import.meta.dir === "string" ? import.meta.dir : dirname(import.meta.url.replace("file://", ""));
12178
+ const tsPath = join5(dir, "scheduled-sync.ts");
12179
+ const jsPath = join5(dir, "scheduled-sync.js");
12180
+ try {
12181
+ if (existsSync5(tsPath))
12182
+ return tsPath;
12183
+ } catch {}
12184
+ return jsPath;
12185
+ }
12186
+ function getBunPath() {
12187
+ const candidates = [
12188
+ join5(homedir5(), ".bun", "bin", "bun"),
12189
+ "/usr/local/bin/bun",
12190
+ "/usr/bin/bun"
12191
+ ];
12192
+ for (const p of candidates) {
12193
+ if (existsSync5(p))
12194
+ return p;
12195
+ }
12196
+ return "bun";
12197
+ }
12198
+ function getLaunchdPlistPath() {
12199
+ return join5(homedir5(), "Library", "LaunchAgents", `com.hasna.cloud-sync.plist`);
12200
+ }
12201
+ function createLaunchdPlist(intervalMinutes) {
12202
+ const workerPath = getWorkerPath();
12203
+ const bunPath = getBunPath();
12204
+ const logPath = join5(CONFIG_DIR3, "sync.log");
12205
+ const errorLogPath = join5(CONFIG_DIR3, "sync-error.log");
12206
+ return `<?xml version="1.0" encoding="UTF-8"?>
12207
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
12208
+ <plist version="1.0">
12209
+ <dict>
12210
+ <key>Label</key>
12211
+ <string>com.hasna.cloud-sync</string>
12212
+ <key>ProgramArguments</key>
12213
+ <array>
12214
+ <string>${bunPath}</string>
12215
+ <string>run</string>
12216
+ <string>${workerPath}</string>
12217
+ </array>
12218
+ <key>StartInterval</key>
12219
+ <integer>${intervalMinutes * 60}</integer>
12220
+ <key>RunAtLoad</key>
12221
+ <true/>
12222
+ <key>StandardOutPath</key>
12223
+ <string>${logPath}</string>
12224
+ <key>StandardErrorPath</key>
12225
+ <string>${errorLogPath}</string>
12226
+ <key>EnvironmentVariables</key>
12227
+ <dict>
12228
+ <key>PATH</key>
12229
+ <string>${process.env.PATH || "/usr/local/bin:/usr/bin:/bin"}</string>
12230
+ <key>HOME</key>
12231
+ <string>${homedir5()}</string>
12232
+ </dict>
12233
+ </dict>
12234
+ </plist>`;
12235
+ }
12236
+ async function registerLaunchd(intervalMinutes) {
12237
+ const plistPath = getLaunchdPlistPath();
12238
+ const plistDir = dirname(plistPath);
12239
+ mkdirSync5(plistDir, { recursive: true });
12240
+ try {
12241
+ await Bun.spawn(["launchctl", "unload", plistPath]).exited;
12242
+ } catch {}
12243
+ writeFileSync3(plistPath, createLaunchdPlist(intervalMinutes));
12244
+ await Bun.spawn(["launchctl", "load", plistPath]).exited;
12245
+ }
12246
+ async function removeLaunchd() {
12247
+ const plistPath = getLaunchdPlistPath();
12248
+ try {
12249
+ await Bun.spawn(["launchctl", "unload", plistPath]).exited;
12250
+ } catch {}
12251
+ try {
12252
+ unlinkSync(plistPath);
12253
+ } catch {}
12254
+ }
12255
+ function getSystemdDir() {
12256
+ return join5(homedir5(), ".config", "systemd", "user");
12257
+ }
12258
+ function createSystemdService() {
12259
+ const workerPath = getWorkerPath();
12260
+ const bunPath = getBunPath();
12261
+ return `[Unit]
12262
+ Description=Hasna Cloud Sync
12263
+ After=network.target
12264
+
12265
+ [Service]
12266
+ Type=oneshot
12267
+ ExecStart=${bunPath} run ${workerPath}
12268
+ Environment=HOME=${homedir5()}
12269
+ Environment=PATH=${process.env.PATH || "/usr/local/bin:/usr/bin:/bin"}
12270
+
12271
+ [Install]
12272
+ WantedBy=default.target
12273
+ `;
12274
+ }
12275
+ function createSystemdTimer(intervalMinutes) {
12276
+ return `[Unit]
12277
+ Description=Hasna Cloud Sync Timer
12278
+
12279
+ [Timer]
12280
+ OnBootSec=${intervalMinutes}min
12281
+ OnUnitActiveSec=${intervalMinutes}min
12282
+ Persistent=true
12283
+
12284
+ [Install]
12285
+ WantedBy=timers.target
12286
+ `;
12287
+ }
12288
+ async function registerSystemd(intervalMinutes) {
12289
+ const dir = getSystemdDir();
12290
+ mkdirSync5(dir, { recursive: true });
12291
+ writeFileSync3(join5(dir, `${SERVICE_NAME}.service`), createSystemdService());
12292
+ writeFileSync3(join5(dir, `${SERVICE_NAME}.timer`), createSystemdTimer(intervalMinutes));
12293
+ await Bun.spawn(["systemctl", "--user", "daemon-reload"]).exited;
12294
+ await Bun.spawn(["systemctl", "--user", "enable", "--now", `${SERVICE_NAME}.timer`]).exited;
12295
+ }
12296
+ async function removeSystemd() {
12297
+ try {
12298
+ await Bun.spawn(["systemctl", "--user", "disable", "--now", `${SERVICE_NAME}.timer`]).exited;
12299
+ } catch {}
12300
+ const dir = getSystemdDir();
12301
+ try {
12302
+ unlinkSync(join5(dir, `${SERVICE_NAME}.service`));
12303
+ } catch {}
12304
+ try {
12305
+ unlinkSync(join5(dir, `${SERVICE_NAME}.timer`));
12306
+ } catch {}
12307
+ try {
12308
+ await Bun.spawn(["systemctl", "--user", "daemon-reload"]).exited;
12309
+ } catch {}
12310
+ }
12184
12311
  async function registerSyncSchedule(intervalMinutes) {
12185
12312
  if (intervalMinutes <= 0) {
12186
12313
  throw new Error("Interval must be a positive number of minutes.");
12187
12314
  }
12188
- const cronExpr = minutesToCron(intervalMinutes);
12189
- const workerPath = getWorkerPath();
12190
- await Bun.cron(workerPath, cronExpr, CRON_TITLE);
12315
+ mkdirSync5(CONFIG_DIR3, { recursive: true });
12316
+ if (platform() === "darwin") {
12317
+ await registerLaunchd(intervalMinutes);
12318
+ } else {
12319
+ await registerSystemd(intervalMinutes);
12320
+ }
12191
12321
  const config = getCloudConfig2();
12192
12322
  config.sync.schedule_minutes = intervalMinutes;
12193
12323
  saveCloudConfig2(config);
12194
12324
  }
12195
12325
  async function removeSyncSchedule() {
12196
- await Bun.cron.remove(CRON_TITLE);
12326
+ if (platform() === "darwin") {
12327
+ await removeLaunchd();
12328
+ } else {
12329
+ await removeSystemd();
12330
+ }
12197
12331
  const config = getCloudConfig2();
12198
12332
  config.sync.schedule_minutes = 0;
12199
12333
  saveCloudConfig2(config);
@@ -12202,17 +12336,26 @@ function getSyncScheduleStatus() {
12202
12336
  const config = getCloudConfig2();
12203
12337
  const minutes = config.sync.schedule_minutes;
12204
12338
  const registered = minutes > 0;
12339
+ let mechanism = "none";
12340
+ if (registered) {
12341
+ if (platform() === "darwin") {
12342
+ mechanism = existsSync5(getLaunchdPlistPath()) ? "launchd" : "none";
12343
+ } else {
12344
+ mechanism = existsSync5(join5(getSystemdDir(), `${SERVICE_NAME}.timer`)) ? "systemd" : "none";
12345
+ }
12346
+ }
12205
12347
  return {
12206
12348
  registered,
12207
12349
  schedule_minutes: minutes,
12208
- cron_expression: registered ? minutesToCron(minutes) : null
12350
+ cron_expression: registered ? minutesToCron(minutes) : null,
12351
+ mechanism
12209
12352
  };
12210
12353
  }
12211
12354
 
12212
12355
  // src/scheduled-sync.ts
12213
12356
  init_config();
12214
12357
  init_adapter();
12215
- import { existsSync as existsSync5, readdirSync as readdirSync3 } from "fs";
12358
+ import { existsSync as existsSync6, readdirSync as readdirSync3 } from "fs";
12216
12359
  import { join as join6 } from "path";
12217
12360
 
12218
12361
  // src/sync-incremental.ts
@@ -12355,7 +12498,7 @@ function discoverSyncableServices() {
12355
12498
  if (!entry.isDirectory())
12356
12499
  continue;
12357
12500
  const dbPath = join6(hasnaDir, entry.name, `${entry.name}.db`);
12358
- if (existsSync5(dbPath)) {
12501
+ if (existsSync6(dbPath)) {
12359
12502
  services.push(entry.name);
12360
12503
  }
12361
12504
  }
@@ -12378,7 +12521,7 @@ async function runScheduledSync() {
12378
12521
  };
12379
12522
  try {
12380
12523
  const dbPath = join6(getDataDir(service), `${service}.db`);
12381
- if (!existsSync5(dbPath)) {
12524
+ if (!existsSync6(dbPath)) {
12382
12525
  continue;
12383
12526
  }
12384
12527
  const local = new SqliteAdapter(dbPath);
@@ -12421,9 +12564,9 @@ async function runScheduledSync() {
12421
12564
  }
12422
12565
 
12423
12566
  // src/discover.ts
12424
- import { readdirSync as readdirSync4, existsSync as existsSync6 } from "fs";
12567
+ import { readdirSync as readdirSync4, existsSync as existsSync7 } from "fs";
12425
12568
  import { join as join7 } from "path";
12426
- import { homedir as homedir5 } from "os";
12569
+ import { homedir as homedir6 } from "os";
12427
12570
  var SYNC_EXCLUDED_TABLE_PATTERNS = [
12428
12571
  /^sqlite_/,
12429
12572
  /_fts$/,
@@ -12435,8 +12578,8 @@ function isSyncExcludedTable(table) {
12435
12578
  return SYNC_EXCLUDED_TABLE_PATTERNS.some((p) => p.test(table));
12436
12579
  }
12437
12580
  function discoverServices() {
12438
- const dataDir = join7(homedir5(), ".hasna");
12439
- if (!existsSync6(dataDir))
12581
+ const dataDir = join7(homedir6(), ".hasna");
12582
+ if (!existsSync7(dataDir))
12440
12583
  return [];
12441
12584
  try {
12442
12585
  const entries = readdirSync4(dataDir, { withFileTypes: true });
package/dist/index.js CHANGED
@@ -9347,15 +9347,15 @@ __export(exports_discover, {
9347
9347
  SYNC_EXCLUDED_TABLE_PATTERNS: () => SYNC_EXCLUDED_TABLE_PATTERNS,
9348
9348
  KNOWN_PG_SERVICES: () => KNOWN_PG_SERVICES
9349
9349
  });
9350
- import { readdirSync as readdirSync3, existsSync as existsSync5 } from "fs";
9350
+ import { readdirSync as readdirSync3, existsSync as existsSync6 } from "fs";
9351
9351
  import { join as join6 } from "path";
9352
- import { homedir as homedir4 } from "os";
9352
+ import { homedir as homedir5 } from "os";
9353
9353
  function isSyncExcludedTable(table) {
9354
9354
  return SYNC_EXCLUDED_TABLE_PATTERNS.some((p) => p.test(table));
9355
9355
  }
9356
9356
  function discoverServices() {
9357
- const dataDir = join6(homedir4(), ".hasna");
9358
- if (!existsSync5(dataDir))
9357
+ const dataDir = join6(homedir5(), ".hasna");
9358
+ if (!existsSync6(dataDir))
9359
9359
  return [];
9360
9360
  try {
9361
9361
  const entries = readdirSync3(dataDir, { withFileTypes: true });
@@ -9376,8 +9376,8 @@ function discoverSyncableServices2() {
9376
9376
  return local.filter((s) => pgSet.has(s));
9377
9377
  }
9378
9378
  function getServiceDbPath(service) {
9379
- const dataDir = join6(homedir4(), ".hasna", service);
9380
- if (!existsSync5(dataDir))
9379
+ const dataDir = join6(homedir5(), ".hasna", service);
9380
+ if (!existsSync6(dataDir))
9381
9381
  return null;
9382
9382
  const candidates = [
9383
9383
  join6(dataDir, `${service}.db`),
@@ -9393,7 +9393,7 @@ function getServiceDbPath(service) {
9393
9393
  }
9394
9394
  } catch {}
9395
9395
  for (const p of candidates) {
9396
- if (existsSync5(p))
9396
+ if (existsSync6(p))
9397
9397
  return p;
9398
9398
  }
9399
9399
  return null;
@@ -10536,18 +10536,10 @@ async function runScheduledSync() {
10536
10536
  // src/sync-schedule.ts
10537
10537
  init_config();
10538
10538
  import { join as join5, dirname } from "path";
10539
- var CRON_TITLE = "hasna-cloud-sync";
10540
- function getWorkerPath() {
10541
- const dir = typeof import.meta.dir === "string" ? import.meta.dir : dirname(import.meta.url.replace("file://", ""));
10542
- const tsPath = join5(dir, "scheduled-sync.ts");
10543
- const jsPath = join5(dir, "scheduled-sync.js");
10544
- try {
10545
- const { existsSync: existsSync5 } = __require("fs");
10546
- if (existsSync5(tsPath))
10547
- return tsPath;
10548
- } catch {}
10549
- return jsPath;
10550
- }
10539
+ import { existsSync as existsSync5, writeFileSync as writeFileSync2, unlinkSync, mkdirSync as mkdirSync3 } from "fs";
10540
+ import { homedir as homedir4, platform } from "os";
10541
+ var SERVICE_NAME = "hasna-cloud-sync";
10542
+ var CONFIG_DIR2 = join5(homedir4(), ".hasna", "cloud");
10551
10543
  function parseInterval(input) {
10552
10544
  const trimmed = input.trim().toLowerCase();
10553
10545
  const hourMatch = trimmed.match(/^(\d+)\s*h$/);
@@ -10586,19 +10578,161 @@ function minutesToCron(minutes) {
10586
10578
  }
10587
10579
  return `*/${minutes} * * * *`;
10588
10580
  }
10581
+ function getWorkerPath() {
10582
+ const dir = typeof import.meta.dir === "string" ? import.meta.dir : dirname(import.meta.url.replace("file://", ""));
10583
+ const tsPath = join5(dir, "scheduled-sync.ts");
10584
+ const jsPath = join5(dir, "scheduled-sync.js");
10585
+ try {
10586
+ if (existsSync5(tsPath))
10587
+ return tsPath;
10588
+ } catch {}
10589
+ return jsPath;
10590
+ }
10591
+ function getBunPath() {
10592
+ const candidates = [
10593
+ join5(homedir4(), ".bun", "bin", "bun"),
10594
+ "/usr/local/bin/bun",
10595
+ "/usr/bin/bun"
10596
+ ];
10597
+ for (const p of candidates) {
10598
+ if (existsSync5(p))
10599
+ return p;
10600
+ }
10601
+ return "bun";
10602
+ }
10603
+ function getLaunchdPlistPath() {
10604
+ return join5(homedir4(), "Library", "LaunchAgents", `com.hasna.cloud-sync.plist`);
10605
+ }
10606
+ function createLaunchdPlist(intervalMinutes) {
10607
+ const workerPath = getWorkerPath();
10608
+ const bunPath = getBunPath();
10609
+ const logPath = join5(CONFIG_DIR2, "sync.log");
10610
+ const errorLogPath = join5(CONFIG_DIR2, "sync-error.log");
10611
+ return `<?xml version="1.0" encoding="UTF-8"?>
10612
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
10613
+ <plist version="1.0">
10614
+ <dict>
10615
+ <key>Label</key>
10616
+ <string>com.hasna.cloud-sync</string>
10617
+ <key>ProgramArguments</key>
10618
+ <array>
10619
+ <string>${bunPath}</string>
10620
+ <string>run</string>
10621
+ <string>${workerPath}</string>
10622
+ </array>
10623
+ <key>StartInterval</key>
10624
+ <integer>${intervalMinutes * 60}</integer>
10625
+ <key>RunAtLoad</key>
10626
+ <true/>
10627
+ <key>StandardOutPath</key>
10628
+ <string>${logPath}</string>
10629
+ <key>StandardErrorPath</key>
10630
+ <string>${errorLogPath}</string>
10631
+ <key>EnvironmentVariables</key>
10632
+ <dict>
10633
+ <key>PATH</key>
10634
+ <string>${process.env.PATH || "/usr/local/bin:/usr/bin:/bin"}</string>
10635
+ <key>HOME</key>
10636
+ <string>${homedir4()}</string>
10637
+ </dict>
10638
+ </dict>
10639
+ </plist>`;
10640
+ }
10641
+ async function registerLaunchd(intervalMinutes) {
10642
+ const plistPath = getLaunchdPlistPath();
10643
+ const plistDir = dirname(plistPath);
10644
+ mkdirSync3(plistDir, { recursive: true });
10645
+ try {
10646
+ await Bun.spawn(["launchctl", "unload", plistPath]).exited;
10647
+ } catch {}
10648
+ writeFileSync2(plistPath, createLaunchdPlist(intervalMinutes));
10649
+ await Bun.spawn(["launchctl", "load", plistPath]).exited;
10650
+ }
10651
+ async function removeLaunchd() {
10652
+ const plistPath = getLaunchdPlistPath();
10653
+ try {
10654
+ await Bun.spawn(["launchctl", "unload", plistPath]).exited;
10655
+ } catch {}
10656
+ try {
10657
+ unlinkSync(plistPath);
10658
+ } catch {}
10659
+ }
10660
+ function getSystemdDir() {
10661
+ return join5(homedir4(), ".config", "systemd", "user");
10662
+ }
10663
+ function createSystemdService() {
10664
+ const workerPath = getWorkerPath();
10665
+ const bunPath = getBunPath();
10666
+ return `[Unit]
10667
+ Description=Hasna Cloud Sync
10668
+ After=network.target
10669
+
10670
+ [Service]
10671
+ Type=oneshot
10672
+ ExecStart=${bunPath} run ${workerPath}
10673
+ Environment=HOME=${homedir4()}
10674
+ Environment=PATH=${process.env.PATH || "/usr/local/bin:/usr/bin:/bin"}
10675
+
10676
+ [Install]
10677
+ WantedBy=default.target
10678
+ `;
10679
+ }
10680
+ function createSystemdTimer(intervalMinutes) {
10681
+ return `[Unit]
10682
+ Description=Hasna Cloud Sync Timer
10683
+
10684
+ [Timer]
10685
+ OnBootSec=${intervalMinutes}min
10686
+ OnUnitActiveSec=${intervalMinutes}min
10687
+ Persistent=true
10688
+
10689
+ [Install]
10690
+ WantedBy=timers.target
10691
+ `;
10692
+ }
10693
+ async function registerSystemd(intervalMinutes) {
10694
+ const dir = getSystemdDir();
10695
+ mkdirSync3(dir, { recursive: true });
10696
+ writeFileSync2(join5(dir, `${SERVICE_NAME}.service`), createSystemdService());
10697
+ writeFileSync2(join5(dir, `${SERVICE_NAME}.timer`), createSystemdTimer(intervalMinutes));
10698
+ await Bun.spawn(["systemctl", "--user", "daemon-reload"]).exited;
10699
+ await Bun.spawn(["systemctl", "--user", "enable", "--now", `${SERVICE_NAME}.timer`]).exited;
10700
+ }
10701
+ async function removeSystemd() {
10702
+ try {
10703
+ await Bun.spawn(["systemctl", "--user", "disable", "--now", `${SERVICE_NAME}.timer`]).exited;
10704
+ } catch {}
10705
+ const dir = getSystemdDir();
10706
+ try {
10707
+ unlinkSync(join5(dir, `${SERVICE_NAME}.service`));
10708
+ } catch {}
10709
+ try {
10710
+ unlinkSync(join5(dir, `${SERVICE_NAME}.timer`));
10711
+ } catch {}
10712
+ try {
10713
+ await Bun.spawn(["systemctl", "--user", "daemon-reload"]).exited;
10714
+ } catch {}
10715
+ }
10589
10716
  async function registerSyncSchedule(intervalMinutes) {
10590
10717
  if (intervalMinutes <= 0) {
10591
10718
  throw new Error("Interval must be a positive number of minutes.");
10592
10719
  }
10593
- const cronExpr = minutesToCron(intervalMinutes);
10594
- const workerPath = getWorkerPath();
10595
- await Bun.cron(workerPath, cronExpr, CRON_TITLE);
10720
+ mkdirSync3(CONFIG_DIR2, { recursive: true });
10721
+ if (platform() === "darwin") {
10722
+ await registerLaunchd(intervalMinutes);
10723
+ } else {
10724
+ await registerSystemd(intervalMinutes);
10725
+ }
10596
10726
  const config = getCloudConfig();
10597
10727
  config.sync.schedule_minutes = intervalMinutes;
10598
10728
  saveCloudConfig(config);
10599
10729
  }
10600
10730
  async function removeSyncSchedule() {
10601
- await Bun.cron.remove(CRON_TITLE);
10731
+ if (platform() === "darwin") {
10732
+ await removeLaunchd();
10733
+ } else {
10734
+ await removeSystemd();
10735
+ }
10602
10736
  const config = getCloudConfig();
10603
10737
  config.sync.schedule_minutes = 0;
10604
10738
  saveCloudConfig(config);
@@ -10607,10 +10741,19 @@ function getSyncScheduleStatus() {
10607
10741
  const config = getCloudConfig();
10608
10742
  const minutes = config.sync.schedule_minutes;
10609
10743
  const registered = minutes > 0;
10744
+ let mechanism = "none";
10745
+ if (registered) {
10746
+ if (platform() === "darwin") {
10747
+ mechanism = existsSync5(getLaunchdPlistPath()) ? "launchd" : "none";
10748
+ } else {
10749
+ mechanism = existsSync5(join5(getSystemdDir(), `${SERVICE_NAME}.timer`)) ? "systemd" : "none";
10750
+ }
10751
+ }
10610
10752
  return {
10611
10753
  registered,
10612
10754
  schedule_minutes: minutes,
10613
- cron_expression: registered ? minutesToCron(minutes) : null
10755
+ cron_expression: registered ? minutesToCron(minutes) : null,
10756
+ mechanism
10614
10757
  };
10615
10758
  }
10616
10759
  // src/pg-migrate.ts
@@ -9,34 +9,28 @@
9
9
  export declare function parseInterval(input: string): number;
10
10
  /**
11
11
  * Convert minutes to a cron expression.
12
- *
13
- * - For intervals that divide evenly into 60: `*\/<n> * * * *`
14
- * - For hourly multiples: `0 *\/<h> * * *`
15
- * - Otherwise: `*\/<n> * * * *` (best-effort)
16
12
  */
17
13
  export declare function minutesToCron(minutes: number): string;
18
14
  export interface SyncScheduleStatus {
19
15
  registered: boolean;
20
16
  schedule_minutes: number;
21
17
  cron_expression: string | null;
18
+ mechanism: "launchd" | "systemd" | "none";
22
19
  }
23
20
  /**
24
- * Register a Bun.cron job that runs the scheduled sync worker on a fixed
25
- * interval.
21
+ * Register a system-level scheduled sync.
26
22
  *
27
- * - Persists `schedule_minutes` in `~/.hasna/cloud/config.json`.
28
- * - Calls `Bun.cron()` to register an OS-level cron job.
23
+ * - macOS: creates a launchd plist in ~/Library/LaunchAgents/
24
+ * - Linux: creates a systemd user timer in ~/.config/systemd/user/
25
+ * - Persists interval in ~/.hasna/cloud/config.json
29
26
  */
30
27
  export declare function registerSyncSchedule(intervalMinutes: number): Promise<void>;
31
28
  /**
32
- * Remove the registered sync cron job.
33
- *
34
- * - Calls `Bun.cron.remove()` to unregister the OS-level job.
35
- * - Sets `schedule_minutes` to 0 in config.
29
+ * Remove the registered sync schedule.
36
30
  */
37
31
  export declare function removeSyncSchedule(): Promise<void>;
38
32
  /**
39
- * Get the current sync schedule status from config.
33
+ * Get the current sync schedule status.
40
34
  */
41
35
  export declare function getSyncScheduleStatus(): SyncScheduleStatus;
42
36
  //# sourceMappingURL=sync-schedule.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sync-schedule.d.ts","sourceRoot":"","sources":["../src/sync-schedule.ts"],"names":[],"mappings":"AAkCA;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAiCnD;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAkBrD;AAMD,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,OAAO,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACxC,eAAe,EAAE,MAAM,GACtB,OAAO,CAAC,IAAI,CAAC,CAef;AAED;;;;;GAKG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,CAOxD;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,kBAAkB,CAU1D"}
1
+ {"version":3,"file":"sync-schedule.d.ts","sourceRoot":"","sources":["../src/sync-schedule.ts"],"names":[],"mappings":"AAgBA;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAiCnD;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAiBrD;AAsKD,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,OAAO,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,SAAS,EAAE,SAAS,GAAG,SAAS,GAAG,MAAM,CAAC;CAC3C;AAED;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACxC,eAAe,EAAE,MAAM,GACtB,OAAO,CAAC,IAAI,CAAC,CAiBf;AAED;;GAEG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,CAUxD;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,kBAAkB,CAoB1D"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/cloud",
3
- "version": "0.1.17",
3
+ "version": "0.1.18",
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",