@hasna/cloud 0.1.31 → 0.1.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/LICENSE +2 -1
  2. package/README.md +17 -0
  3. package/dist/adapter.test.d.ts +2 -0
  4. package/dist/adapter.test.d.ts.map +1 -0
  5. package/dist/auto-sync.d.ts.map +1 -1
  6. package/dist/cli/cmd-doctor.d.ts +3 -0
  7. package/dist/cli/cmd-doctor.d.ts.map +1 -0
  8. package/dist/cli/cmd-feedback.d.ts +3 -0
  9. package/dist/cli/cmd-feedback.d.ts.map +1 -0
  10. package/dist/cli/cmd-migrate.d.ts +3 -0
  11. package/dist/cli/cmd-migrate.d.ts.map +1 -0
  12. package/dist/cli/cmd-setup.d.ts +3 -0
  13. package/dist/cli/cmd-setup.d.ts.map +1 -0
  14. package/dist/cli/cmd-sync.d.ts +4 -0
  15. package/dist/cli/cmd-sync.d.ts.map +1 -0
  16. package/dist/cli/index.js +2330 -1079
  17. package/dist/config.d.ts +138 -4
  18. package/dist/config.d.ts.map +1 -1
  19. package/dist/daemon-sync.d.ts +108 -0
  20. package/dist/daemon-sync.d.ts.map +1 -0
  21. package/dist/dialect.test.d.ts +2 -0
  22. package/dist/dialect.test.d.ts.map +1 -0
  23. package/dist/discover.test.d.ts +2 -0
  24. package/dist/discover.test.d.ts.map +1 -0
  25. package/dist/index.d.ts +3 -1
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +1143 -153
  28. package/dist/machines.d.ts +63 -0
  29. package/dist/machines.d.ts.map +1 -0
  30. package/dist/mcp/http.d.ts +27 -0
  31. package/dist/mcp/http.d.ts.map +1 -0
  32. package/dist/mcp/index.d.ts +2 -1
  33. package/dist/mcp/index.d.ts.map +1 -1
  34. package/dist/mcp/index.js +2125 -438
  35. package/dist/scheduled-sync.js +205 -44
  36. package/dist/sync-conflicts.test.d.ts +2 -0
  37. package/dist/sync-conflicts.test.d.ts.map +1 -0
  38. package/dist/sync-incremental.d.ts +5 -0
  39. package/dist/sync-incremental.d.ts.map +1 -1
  40. package/dist/sync-schedule.test.d.ts +2 -0
  41. package/dist/sync-schedule.test.d.ts.map +1 -0
  42. package/dist/sync.d.ts.map +1 -1
  43. package/dist/sync.test.d.ts +2 -0
  44. package/dist/sync.test.d.ts.map +1 -0
  45. package/package.json +1 -1
package/dist/mcp/index.js CHANGED
@@ -10596,7 +10596,7 @@ var require_arrayParser = __commonJS((exports, module) => {
10596
10596
  };
10597
10597
  });
10598
10598
 
10599
- // node_modules/pg-types/node_modules/postgres-date/index.js
10599
+ // node_modules/postgres-date/index.js
10600
10600
  var require_postgres_date = __commonJS((exports, module) => {
10601
10601
  var DATE_TIME = /(\d{1,})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})(\.\d{1,})?.*?( BC)?$/;
10602
10602
  var DATE = /^(\d{1,})-(\d{2})-(\d{2})( BC)?$/;
@@ -10698,7 +10698,7 @@ var require_mutable = __commonJS((exports, module) => {
10698
10698
  }
10699
10699
  });
10700
10700
 
10701
- // node_modules/pg-types/node_modules/postgres-interval/index.js
10701
+ // node_modules/postgres-interval/index.js
10702
10702
  var require_postgres_interval = __commonJS((exports, module) => {
10703
10703
  var extend2 = require_mutable();
10704
10704
  module.exports = PostgresInterval;
@@ -10790,7 +10790,7 @@ var require_postgres_interval = __commonJS((exports, module) => {
10790
10790
  }
10791
10791
  });
10792
10792
 
10793
- // node_modules/pg-types/node_modules/postgres-bytea/index.js
10793
+ // node_modules/postgres-bytea/index.js
10794
10794
  var require_postgres_bytea = __commonJS((exports, module) => {
10795
10795
  var bufferFrom = Buffer.from || Buffer;
10796
10796
  module.exports = function parseBytea(input) {
@@ -11814,7 +11814,7 @@ var require_cert_signatures = __commonJS((exports, module) => {
11814
11814
 
11815
11815
  // node_modules/pg/lib/crypto/sasl.js
11816
11816
  var require_sasl = __commonJS((exports, module) => {
11817
- var crypto = require_utils3();
11817
+ var crypto2 = require_utils3();
11818
11818
  var { signatureAlgorithmHashFromCertificate } = require_cert_signatures();
11819
11819
  function startSession(mechanisms, stream) {
11820
11820
  const candidates = ["SCRAM-SHA-256"];
@@ -11827,7 +11827,7 @@ var require_sasl = __commonJS((exports, module) => {
11827
11827
  if (mechanism === "SCRAM-SHA-256-PLUS" && typeof stream.getPeerCertificate !== "function") {
11828
11828
  throw new Error("SASL: Mechanism SCRAM-SHA-256-PLUS requires a certificate");
11829
11829
  }
11830
- const clientNonce = crypto.randomBytes(18).toString("base64");
11830
+ const clientNonce = crypto2.randomBytes(18).toString("base64");
11831
11831
  const gs2Header = mechanism === "SCRAM-SHA-256-PLUS" ? "p=tls-server-end-point" : stream ? "y" : "n";
11832
11832
  return {
11833
11833
  mechanism,
@@ -11863,20 +11863,20 @@ var require_sasl = __commonJS((exports, module) => {
11863
11863
  let hashName = signatureAlgorithmHashFromCertificate(peerCert);
11864
11864
  if (hashName === "MD5" || hashName === "SHA-1")
11865
11865
  hashName = "SHA-256";
11866
- const certHash = await crypto.hashByName(hashName, peerCert);
11866
+ const certHash = await crypto2.hashByName(hashName, peerCert);
11867
11867
  const bindingData = Buffer.concat([Buffer.from("p=tls-server-end-point,,"), Buffer.from(certHash)]);
11868
11868
  channelBinding = bindingData.toString("base64");
11869
11869
  }
11870
11870
  const clientFinalMessageWithoutProof = "c=" + channelBinding + ",r=" + sv.nonce;
11871
11871
  const authMessage = clientFirstMessageBare + "," + serverFirstMessage + "," + clientFinalMessageWithoutProof;
11872
11872
  const saltBytes = Buffer.from(sv.salt, "base64");
11873
- const saltedPassword = await crypto.deriveKey(password, saltBytes, sv.iteration);
11874
- const clientKey = await crypto.hmacSha256(saltedPassword, "Client Key");
11875
- const storedKey = await crypto.sha256(clientKey);
11876
- const clientSignature = await crypto.hmacSha256(storedKey, authMessage);
11873
+ const saltedPassword = await crypto2.deriveKey(password, saltBytes, sv.iteration);
11874
+ const clientKey = await crypto2.hmacSha256(saltedPassword, "Client Key");
11875
+ const storedKey = await crypto2.sha256(clientKey);
11876
+ const clientSignature = await crypto2.hmacSha256(storedKey, authMessage);
11877
11877
  const clientProof = xorBuffers(Buffer.from(clientKey), Buffer.from(clientSignature)).toString("base64");
11878
- const serverKey = await crypto.hmacSha256(saltedPassword, "Server Key");
11879
- const serverSignatureBytes = await crypto.hmacSha256(serverKey, authMessage);
11878
+ const serverKey = await crypto2.hmacSha256(saltedPassword, "Server Key");
11879
+ const serverSignatureBytes = await crypto2.hmacSha256(serverKey, authMessage);
11880
11880
  session.message = "SASLResponse";
11881
11881
  session.serverSignature = Buffer.from(serverSignatureBytes).toString("base64");
11882
11882
  session.response = clientFinalMessageWithoutProof + ",p=" + clientProof;
@@ -13930,7 +13930,7 @@ var require_client = __commonJS((exports, module) => {
13930
13930
  var Query = require_query();
13931
13931
  var defaults = require_defaults2();
13932
13932
  var Connection = require_connection();
13933
- var crypto = require_utils3();
13933
+ var crypto2 = require_utils3();
13934
13934
  var activeQueryDeprecationNotice = nodeUtils.deprecate(() => {}, "Client.activeQuery is deprecated and will be removed in pg@9.0");
13935
13935
  var queryQueueDeprecationNotice = nodeUtils.deprecate(() => {}, "Client.queryQueue is deprecated and will be removed in pg@9.0.");
13936
13936
  var pgPassDeprecationNotice = nodeUtils.deprecate(() => {}, "pgpass support is deprecated and will be removed in pg@9.0. " + "You can provide an async function as the password property to the Client/Pool constructor that returns a password instead. Within this function you can call the pgpass module in your own code.");
@@ -14146,7 +14146,7 @@ var require_client = __commonJS((exports, module) => {
14146
14146
  _handleAuthMD5Password(msg) {
14147
14147
  this._getPassword(async () => {
14148
14148
  try {
14149
- const hashedPassword = await crypto.postgresMd5PasswordHash(this.user, this.password, msg.salt);
14149
+ const hashedPassword = await crypto2.postgresMd5PasswordHash(this.user, this.password, msg.salt);
14150
14150
  this.connection.password(hashedPassword);
14151
14151
  } catch (e) {
14152
14152
  this.emit("error", e);
@@ -15697,6 +15697,520 @@ var init_dotfile = __esm(() => {
15697
15697
  HASNA_DIR = join(homedir(), ".hasna");
15698
15698
  });
15699
15699
 
15700
+ // src/discover.ts
15701
+ var exports_discover = {};
15702
+ __export(exports_discover, {
15703
+ isSyncExcludedTable: () => isSyncExcludedTable,
15704
+ getServiceDbPath: () => getServiceDbPath,
15705
+ discoverSyncableServices: () => discoverSyncableServices,
15706
+ discoverServices: () => discoverServices,
15707
+ SYNC_EXCLUDED_TABLE_PATTERNS: () => SYNC_EXCLUDED_TABLE_PATTERNS,
15708
+ KNOWN_PG_SERVICES: () => KNOWN_PG_SERVICES
15709
+ });
15710
+ import { readdirSync as readdirSync2, existsSync as existsSync2 } from "fs";
15711
+ import { join as join2 } from "path";
15712
+ import { homedir as homedir2 } from "os";
15713
+ function isSyncExcludedTable(table) {
15714
+ return SYNC_EXCLUDED_TABLE_PATTERNS.some((p) => p.test(table));
15715
+ }
15716
+ function discoverServices() {
15717
+ const dataDir = join2(homedir2(), ".hasna");
15718
+ if (!existsSync2(dataDir))
15719
+ return [];
15720
+ try {
15721
+ const entries = readdirSync2(dataDir, { withFileTypes: true });
15722
+ return entries.filter((e) => {
15723
+ if (!e.isDirectory())
15724
+ return false;
15725
+ if (e.name === "cloud" || e.name.startsWith("."))
15726
+ return false;
15727
+ return true;
15728
+ }).map((e) => e.name).sort();
15729
+ } catch {
15730
+ return [];
15731
+ }
15732
+ }
15733
+ function discoverSyncableServices() {
15734
+ const local = discoverServices();
15735
+ const pgSet = new Set(KNOWN_PG_SERVICES);
15736
+ return local.filter((s) => pgSet.has(s));
15737
+ }
15738
+ function getServiceDbPath(service) {
15739
+ const dataDir = join2(homedir2(), ".hasna", service);
15740
+ if (!existsSync2(dataDir))
15741
+ return null;
15742
+ const candidates = [
15743
+ join2(dataDir, `${service}.db`),
15744
+ join2(dataDir, "data.db"),
15745
+ join2(dataDir, "database.db")
15746
+ ];
15747
+ try {
15748
+ const files = readdirSync2(dataDir);
15749
+ for (const f of files) {
15750
+ if (f.endsWith(".db") && !f.endsWith("-wal") && !f.endsWith("-shm")) {
15751
+ candidates.push(join2(dataDir, f));
15752
+ }
15753
+ }
15754
+ } catch {}
15755
+ for (const p of candidates) {
15756
+ if (existsSync2(p))
15757
+ return p;
15758
+ }
15759
+ return null;
15760
+ }
15761
+ var KNOWN_PG_SERVICES, SYNC_EXCLUDED_TABLE_PATTERNS;
15762
+ var init_discover = __esm(() => {
15763
+ KNOWN_PG_SERVICES = [
15764
+ "assistants",
15765
+ "attachments",
15766
+ "brains",
15767
+ "configs",
15768
+ "connectors",
15769
+ "contacts",
15770
+ "context",
15771
+ "conversations",
15772
+ "crawl",
15773
+ "deployment",
15774
+ "economy",
15775
+ "emails",
15776
+ "files",
15777
+ "hooks",
15778
+ "implementations",
15779
+ "logs",
15780
+ "mcps",
15781
+ "mementos",
15782
+ "microservices",
15783
+ "predictor",
15784
+ "prompts",
15785
+ "recordings",
15786
+ "researcher",
15787
+ "sandboxes",
15788
+ "search",
15789
+ "secrets",
15790
+ "sessions",
15791
+ "signatures",
15792
+ "skills",
15793
+ "telephony",
15794
+ "terminal",
15795
+ "testers",
15796
+ "tickets",
15797
+ "todos",
15798
+ "wallets"
15799
+ ];
15800
+ SYNC_EXCLUDED_TABLE_PATTERNS = [
15801
+ /^sqlite_/,
15802
+ /_fts$/,
15803
+ /_fts_/,
15804
+ /^_sync_/,
15805
+ /^_pg_migrations$/
15806
+ ];
15807
+ });
15808
+
15809
+ // src/machines.ts
15810
+ import { spawnSync } from "child_process";
15811
+ import { existsSync as existsSync3 } from "fs";
15812
+ import { homedir as homedir3, hostname as hostname2, platform, arch, userInfo } from "os";
15813
+ import { dirname, join as join3 } from "path";
15814
+ function quoteSqlString(value) {
15815
+ return `'${value.replace(/'/g, "''")}'`;
15816
+ }
15817
+ function normalizePlatform(value) {
15818
+ if (value === "darwin")
15819
+ return "macos";
15820
+ if (value === "win32")
15821
+ return "windows";
15822
+ return value;
15823
+ }
15824
+ function detectWorkspacePath() {
15825
+ const home = homedir3();
15826
+ const candidates = [join3(home, "workspace"), join3(home, "Workspace")];
15827
+ for (const candidate of candidates) {
15828
+ if (existsSync3(candidate))
15829
+ return candidate;
15830
+ }
15831
+ const cwd = process.cwd();
15832
+ const workspaceIdx = cwd.indexOf("/workspace/");
15833
+ if (workspaceIdx >= 0) {
15834
+ return cwd.slice(0, workspaceIdx + "/workspace".length);
15835
+ }
15836
+ const workspaceUpperIdx = cwd.indexOf("/Workspace/");
15837
+ if (workspaceUpperIdx >= 0) {
15838
+ return cwd.slice(0, workspaceUpperIdx + "/Workspace".length);
15839
+ }
15840
+ return cwd;
15841
+ }
15842
+ function detectBunPath() {
15843
+ return dirname(process.execPath);
15844
+ }
15845
+ function toFlag(value, fallback = 0) {
15846
+ if (value === undefined)
15847
+ return fallback;
15848
+ return value ? 1 : 0;
15849
+ }
15850
+ function getCurrentMachineId() {
15851
+ return hostname2();
15852
+ }
15853
+ function detectCurrentMachine(opts = {}) {
15854
+ const id = opts.id ?? getCurrentMachineId();
15855
+ const username = userInfo().username;
15856
+ return {
15857
+ id,
15858
+ ssh_address: opts.ssh_address ?? `${username}@${id}`,
15859
+ arch: opts.arch ?? `${normalizePlatform(platform())}-${arch()}`,
15860
+ workspace_path: opts.workspace_path ?? detectWorkspacePath(),
15861
+ bun_path: opts.bun_path ?? detectBunPath(),
15862
+ is_primary: opts.is_primary,
15863
+ archived: opts.archived,
15864
+ last_seen_at: opts.last_seen_at,
15865
+ registered_at: opts.registered_at
15866
+ };
15867
+ }
15868
+ function ensureMachinesTable(db) {
15869
+ db.exec(MACHINES_TABLE_SQL);
15870
+ }
15871
+ function getMachineRecord(db, id) {
15872
+ ensureMachinesTable(db);
15873
+ return db.get(`SELECT id, ssh_address, arch, workspace_path, bun_path, is_primary, last_seen_at, registered_at, archived
15874
+ FROM machines
15875
+ WHERE id = ?`, id) ?? null;
15876
+ }
15877
+ function registerMachine(db, opts = {}) {
15878
+ ensureMachinesTable(db);
15879
+ const detected = detectCurrentMachine(opts);
15880
+ const id = detected.id ?? getCurrentMachineId();
15881
+ const now = new Date().toISOString();
15882
+ const existing = getMachineRecord(db, id);
15883
+ const isPrimary = toFlag(detected.is_primary, existing?.is_primary ?? 0);
15884
+ const archived = toFlag(detected.archived, existing?.archived ?? 0);
15885
+ if (isPrimary === 1 && archived === 1) {
15886
+ throw new Error(`Primary machine "${id}" cannot be archived.`);
15887
+ }
15888
+ const record3 = {
15889
+ id,
15890
+ ssh_address: detected.ssh_address ?? existing?.ssh_address ?? "",
15891
+ arch: detected.arch ?? existing?.arch ?? "",
15892
+ workspace_path: detected.workspace_path ?? existing?.workspace_path ?? "",
15893
+ bun_path: detected.bun_path ?? existing?.bun_path ?? "",
15894
+ is_primary: isPrimary,
15895
+ last_seen_at: detected.last_seen_at ?? now,
15896
+ registered_at: existing?.registered_at ?? detected.registered_at ?? now,
15897
+ archived
15898
+ };
15899
+ db.run(`INSERT INTO machines (
15900
+ id,
15901
+ ssh_address,
15902
+ arch,
15903
+ workspace_path,
15904
+ bun_path,
15905
+ is_primary,
15906
+ last_seen_at,
15907
+ registered_at,
15908
+ archived
15909
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
15910
+ ON CONFLICT(id) DO UPDATE SET
15911
+ ssh_address = excluded.ssh_address,
15912
+ arch = excluded.arch,
15913
+ workspace_path = excluded.workspace_path,
15914
+ bun_path = excluded.bun_path,
15915
+ is_primary = excluded.is_primary,
15916
+ last_seen_at = excluded.last_seen_at,
15917
+ registered_at = excluded.registered_at,
15918
+ archived = excluded.archived`, record3.id, record3.ssh_address, record3.arch, record3.workspace_path, record3.bun_path, record3.is_primary, record3.last_seen_at, record3.registered_at, record3.archived);
15919
+ return getMachineRecord(db, record3.id) ?? record3;
15920
+ }
15921
+ function listMachines(db, opts = {}) {
15922
+ ensureMachinesTable(db);
15923
+ const includeArchived = opts.includeArchived ?? false;
15924
+ const whereClause = includeArchived ? "" : "WHERE archived = 0";
15925
+ return db.all(`SELECT id, ssh_address, arch, workspace_path, bun_path, is_primary, last_seen_at, registered_at, archived
15926
+ FROM machines
15927
+ ${whereClause}
15928
+ ORDER BY is_primary DESC, id ASC`);
15929
+ }
15930
+ function pingMachine(machine) {
15931
+ const record3 = typeof machine === "string" ? {
15932
+ id: machine,
15933
+ ssh_address: machine
15934
+ } : machine;
15935
+ const startedAt = Date.now();
15936
+ const checkedAt = new Date().toISOString();
15937
+ const currentId = getCurrentMachineId();
15938
+ if (record3.id === currentId) {
15939
+ return {
15940
+ id: record3.id,
15941
+ online: true,
15942
+ checked_at: checkedAt,
15943
+ latency_ms: Date.now() - startedAt
15944
+ };
15945
+ }
15946
+ const target = record3.ssh_address || record3.id;
15947
+ if (!target) {
15948
+ return {
15949
+ id: record3.id,
15950
+ online: false,
15951
+ error: "Machine has no ssh target.",
15952
+ checked_at: checkedAt,
15953
+ latency_ms: Date.now() - startedAt
15954
+ };
15955
+ }
15956
+ const result = spawnSync("ssh", ["-o", "BatchMode=yes", "-o", "ConnectTimeout=5", target, "true"], {
15957
+ encoding: "utf-8",
15958
+ timeout: 6000
15959
+ });
15960
+ return {
15961
+ id: record3.id,
15962
+ online: result.status === 0,
15963
+ error: result.status === 0 ? undefined : (result.stderr || result.error?.message || "SSH health check failed").trim(),
15964
+ checked_at: checkedAt,
15965
+ latency_ms: Date.now() - startedAt
15966
+ };
15967
+ }
15968
+ function getMachineStatus(db, opts = {}) {
15969
+ return listMachines(db, opts).map((machine) => pingMachine(machine));
15970
+ }
15971
+ function tableExists(db, table) {
15972
+ try {
15973
+ if (typeof db.query === "function") {
15974
+ const rows2 = db.all(`SELECT name FROM sqlite_master WHERE type='table' AND name = ?`, table);
15975
+ return rows2.length > 0;
15976
+ }
15977
+ const rows = db.all(`SELECT table_name
15978
+ FROM information_schema.tables
15979
+ WHERE table_schema = 'public' AND table_name = ?`, table);
15980
+ return rows.length > 0;
15981
+ } catch {
15982
+ return false;
15983
+ }
15984
+ }
15985
+ function hasMachineIdColumn(db, table) {
15986
+ try {
15987
+ if (typeof db.query === "function") {
15988
+ const rows2 = db.all(`PRAGMA table_info("${table}")`);
15989
+ return rows2.some((row) => row.name === "machine_id");
15990
+ }
15991
+ const rows = db.all(`SELECT column_name
15992
+ FROM information_schema.columns
15993
+ WHERE table_schema = 'public' AND table_name = ? AND column_name = 'machine_id'`, table);
15994
+ return rows.length > 0;
15995
+ } catch {
15996
+ return false;
15997
+ }
15998
+ }
15999
+ function shouldTrackMachineId(table) {
16000
+ return table !== "machines" && !isSyncExcludedTable(table);
16001
+ }
16002
+ function ensureMachineIdColumn(db, table) {
16003
+ if (!shouldTrackMachineId(table) || !tableExists(db, table)) {
16004
+ return false;
16005
+ }
16006
+ if (hasMachineIdColumn(db, table)) {
16007
+ return false;
16008
+ }
16009
+ db.exec(`ALTER TABLE "${table}" ADD COLUMN machine_id TEXT DEFAULT ''`);
16010
+ return true;
16011
+ }
16012
+ function extractTableName(sql, pattern) {
16013
+ const match = sql.match(pattern);
16014
+ if (!match)
16015
+ return null;
16016
+ return match[2] ?? match[3] ?? null;
16017
+ }
16018
+ function appendLiteralToValueTuples(sql, literal3) {
16019
+ let output = "";
16020
+ let depth = 0;
16021
+ let inSingle = false;
16022
+ let inDouble = false;
16023
+ let sawTuple = false;
16024
+ let inValues = true;
16025
+ for (let i = 0;i < sql.length; i++) {
16026
+ const char = sql[i];
16027
+ const prev = sql[i - 1];
16028
+ if (!inDouble && char === "'" && prev !== "\\") {
16029
+ inSingle = !inSingle;
16030
+ output += char;
16031
+ continue;
16032
+ }
16033
+ if (!inSingle && char === `"` && prev !== "\\") {
16034
+ inDouble = !inDouble;
16035
+ output += char;
16036
+ continue;
16037
+ }
16038
+ if (inSingle || inDouble) {
16039
+ output += char;
16040
+ continue;
16041
+ }
16042
+ if (inValues && char === "(") {
16043
+ depth += 1;
16044
+ if (depth === 1)
16045
+ sawTuple = true;
16046
+ output += char;
16047
+ continue;
16048
+ }
16049
+ if (inValues && char === ")" && depth === 1) {
16050
+ output += `, ${literal3})`;
16051
+ depth = 0;
16052
+ continue;
16053
+ }
16054
+ if (inValues && char === ")" && depth > 1) {
16055
+ depth -= 1;
16056
+ output += char;
16057
+ continue;
16058
+ }
16059
+ if (inValues && depth === 0 && sawTuple && /[A-Za-z]/.test(char)) {
16060
+ inValues = false;
16061
+ }
16062
+ output += char;
16063
+ }
16064
+ return sawTuple ? output : null;
16065
+ }
16066
+ function rewriteInsertSql(sql, machineId) {
16067
+ const match = sql.match(/^\s*(insert(?:\s+or\s+\w+)?\s+into\s+((?:"([^"]+)")|([A-Za-z_][\w$]*))\s*)\(([^)]*)\)(\s*values\s*)([\s\S]*)$/i);
16068
+ if (!match)
16069
+ return null;
16070
+ const table = match[3] ?? match[4];
16071
+ if (!table || !shouldTrackMachineId(table))
16072
+ return null;
16073
+ const columns = match[5].split(",").map((column) => column.trim().replace(/^"|"$/g, ""));
16074
+ if (columns.some((column) => column.toLowerCase() === "machine_id")) {
16075
+ return { table, sql };
16076
+ }
16077
+ const rewrittenValues = appendLiteralToValueTuples(match[7], quoteSqlString(machineId));
16078
+ if (!rewrittenValues)
16079
+ return { table, sql };
16080
+ const nextColumns = `${match[5].trim()}, "machine_id"`;
16081
+ return {
16082
+ table,
16083
+ sql: `${match[1]}(${nextColumns})${match[6]}${rewrittenValues}`
16084
+ };
16085
+ }
16086
+ function rewriteUpdateSql(sql, machineId) {
16087
+ const match = sql.match(/^\s*(update\s+((?:"([^"]+)")|([A-Za-z_][\w$]*))\s+set\s*)([\s\S]*?)(\s+(?:where|returning)\b[\s\S]*|\s*)$/i);
16088
+ if (!match)
16089
+ return null;
16090
+ const table = match[3] ?? match[4];
16091
+ if (!table || !shouldTrackMachineId(table))
16092
+ return null;
16093
+ if (/\bmachine_id\b/i.test(match[5])) {
16094
+ return { table, sql };
16095
+ }
16096
+ return {
16097
+ table,
16098
+ sql: `${match[1]}${match[5].trimEnd()}, "machine_id" = ${quoteSqlString(machineId)}${match[6]}`
16099
+ };
16100
+ }
16101
+ function maybeRewriteMachineSql(db, sql, machineId) {
16102
+ const trimmed = sql.trimStart();
16103
+ if (/^insert\b/i.test(trimmed)) {
16104
+ const rewritten = rewriteInsertSql(sql, machineId);
16105
+ if (rewritten?.table) {
16106
+ ensureMachineIdColumn(db, rewritten.table);
16107
+ return rewritten.sql;
16108
+ }
16109
+ return sql;
16110
+ }
16111
+ if (/^update\b/i.test(trimmed)) {
16112
+ const rewritten = rewriteUpdateSql(sql, machineId);
16113
+ if (rewritten?.table) {
16114
+ ensureMachineIdColumn(db, rewritten.table);
16115
+ return rewritten.sql;
16116
+ }
16117
+ return sql;
16118
+ }
16119
+ return sql;
16120
+ }
16121
+ function maybeEnsureCreatedTableHasMachineId(db, sql) {
16122
+ const table = extractTableName(sql, /^\s*(create\s+table(?:\s+if\s+not\s+exists)?\s+((?:"([^"]+)")|([A-Za-z_][\w$]*)))/i);
16123
+ if (table) {
16124
+ ensureMachineIdColumn(db, table);
16125
+ }
16126
+ }
16127
+ function createMachineRegistry(db, machineId = getCurrentMachineId()) {
16128
+ return {
16129
+ register(opts = {}) {
16130
+ return registerMachine(db, { ...opts, id: opts.id ?? machineId });
16131
+ },
16132
+ list(opts = {}) {
16133
+ return listMachines(db, opts);
16134
+ },
16135
+ ping(machine) {
16136
+ if (!machine) {
16137
+ return pingMachine(registerMachine(db, { id: machineId }));
16138
+ }
16139
+ return pingMachine(typeof machine === "string" ? getMachineRecord(db, machine) ?? machine : machine);
16140
+ },
16141
+ status(opts = {}) {
16142
+ return getMachineStatus(db, opts);
16143
+ },
16144
+ currentMachine() {
16145
+ return registerMachine(db, { id: machineId });
16146
+ }
16147
+ };
16148
+ }
16149
+ function createMachineAwareAdapter(db) {
16150
+ ensureMachinesTable(db);
16151
+ const machineId = registerMachine(db).id;
16152
+ const machines = createMachineRegistry(db, machineId);
16153
+ const wrapped = {
16154
+ machine_id: machineId,
16155
+ machines,
16156
+ run(sql, ...params) {
16157
+ return db.run(maybeRewriteMachineSql(db, sql, machineId), ...params);
16158
+ },
16159
+ get(sql, ...params) {
16160
+ return db.get(sql, ...params);
16161
+ },
16162
+ all(sql, ...params) {
16163
+ return db.all(sql, ...params);
16164
+ },
16165
+ exec(sql) {
16166
+ db.exec(sql);
16167
+ maybeEnsureCreatedTableHasMachineId(db, sql);
16168
+ },
16169
+ prepare(sql) {
16170
+ const statement = db.prepare(maybeRewriteMachineSql(db, sql, machineId));
16171
+ return {
16172
+ run(...params) {
16173
+ return statement.run(...params);
16174
+ },
16175
+ get(...params) {
16176
+ return statement.get(...params);
16177
+ },
16178
+ all(...params) {
16179
+ return statement.all(...params);
16180
+ },
16181
+ finalize() {
16182
+ statement.finalize();
16183
+ }
16184
+ };
16185
+ },
16186
+ close() {
16187
+ db.close();
16188
+ },
16189
+ transaction(fn) {
16190
+ return db.transaction(fn);
16191
+ },
16192
+ raw: db.raw,
16193
+ query: typeof db.query === "function" ? db.query.bind(db) : undefined
16194
+ };
16195
+ return wrapped;
16196
+ }
16197
+ var MACHINES_TABLE_SQL = `
16198
+ CREATE TABLE IF NOT EXISTS machines (
16199
+ id TEXT PRIMARY KEY,
16200
+ ssh_address TEXT DEFAULT '',
16201
+ arch TEXT DEFAULT '',
16202
+ workspace_path TEXT DEFAULT '',
16203
+ bun_path TEXT DEFAULT '',
16204
+ is_primary INTEGER DEFAULT 0 CHECK (is_primary IN (0, 1)),
16205
+ last_seen_at TEXT,
16206
+ registered_at TEXT,
16207
+ archived INTEGER DEFAULT 0 CHECK (archived IN (0, 1)),
16208
+ CHECK (NOT (is_primary = 1 AND archived = 1))
16209
+ )`;
16210
+ var init_machines = __esm(() => {
16211
+ init_discover();
16212
+ });
16213
+
15700
16214
  // src/config.ts
15701
16215
  var exports_config = {};
15702
16216
  __export(exports_config, {
@@ -15708,9 +16222,9 @@ __export(exports_config, {
15708
16222
  createDatabase: () => createDatabase2,
15709
16223
  CloudConfigSchema: () => CloudConfigSchema2
15710
16224
  });
15711
- import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
15712
- import { homedir as homedir3 } from "os";
15713
- import { join as join3 } from "path";
16225
+ import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
16226
+ import { homedir as homedir5 } from "os";
16227
+ import { join as join5 } from "path";
15714
16228
  function getConfigDir() {
15715
16229
  return CONFIG_DIR2;
15716
16230
  }
@@ -15718,7 +16232,7 @@ function getConfigPath() {
15718
16232
  return CONFIG_PATH2;
15719
16233
  }
15720
16234
  function getCloudConfig2() {
15721
- if (!existsSync3(CONFIG_PATH2)) {
16235
+ if (!existsSync5(CONFIG_PATH2)) {
15722
16236
  return CloudConfigSchema2.parse({});
15723
16237
  }
15724
16238
  try {
@@ -15751,16 +16265,32 @@ function createDatabase2(options) {
15751
16265
  const mode = options.mode ?? config2.mode;
15752
16266
  if (mode === "cloud") {
15753
16267
  const connStr = options.pgConnectionString ?? getConnectionString2(options.service);
15754
- return new PgAdapter(connStr);
16268
+ return createMachineAwareAdapter(new PgAdapter(connStr));
15755
16269
  }
15756
16270
  const dbPath = options.sqlitePath ?? getDbPath(options.service);
15757
- return new SqliteAdapter(dbPath);
16271
+ return createMachineAwareAdapter(new SqliteAdapter(dbPath));
15758
16272
  }
15759
- var CloudConfigSchema2, CONFIG_DIR2, CONFIG_PATH2;
16273
+ var DaemonConfigSchema2, CloudConfigSchema2, CONFIG_DIR2, CONFIG_PATH2;
15760
16274
  var init_config = __esm(() => {
15761
16275
  init_zod();
15762
16276
  init_adapter();
15763
16277
  init_dotfile();
16278
+ init_machines();
16279
+ DaemonConfigSchema2 = exports_external.object({
16280
+ enabled: exports_external.boolean().default(false),
16281
+ paused: exports_external.boolean().default(false),
16282
+ watch_interval_seconds: exports_external.number().int().positive().default(5),
16283
+ pull_interval_seconds: exports_external.number().int().positive().default(60),
16284
+ push_debounce_seconds: exports_external.number().int().positive().default(5),
16285
+ conflict_strategy: exports_external.enum(["newest-wins", "local-wins", "remote-wins"]).default("newest-wins"),
16286
+ services: exports_external.array(exports_external.string()).default([]),
16287
+ table_intervals: exports_external.record(exports_external.string(), exports_external.record(exports_external.string(), exports_external.number().int().positive())).default({}),
16288
+ file_rules: exports_external.array(exports_external.object({
16289
+ path: exports_external.string(),
16290
+ interval_seconds: exports_external.number().int().positive().default(30),
16291
+ enabled: exports_external.boolean().default(true)
16292
+ })).default([])
16293
+ }).default({});
15764
16294
  CloudConfigSchema2 = exports_external.object({
15765
16295
  rds: exports_external.object({
15766
16296
  host: exports_external.string().default(""),
@@ -15774,34 +16304,35 @@ var init_config = __esm(() => {
15774
16304
  feedback_endpoint: exports_external.string().default("https://feedback.hasna.com/api/v1/feedback"),
15775
16305
  sync: exports_external.object({
15776
16306
  schedule_minutes: exports_external.number().default(0)
15777
- }).default({})
16307
+ }).default({}),
16308
+ daemon: DaemonConfigSchema2
15778
16309
  });
15779
- CONFIG_DIR2 = join3(homedir3(), ".hasna", "cloud");
15780
- CONFIG_PATH2 = join3(CONFIG_DIR2, "config.json");
16310
+ CONFIG_DIR2 = join5(homedir5(), ".hasna", "cloud");
16311
+ CONFIG_PATH2 = join5(CONFIG_DIR2, "config.json");
15781
16312
  });
15782
16313
 
15783
16314
  // src/discover.ts
15784
- var exports_discover = {};
15785
- __export(exports_discover, {
15786
- isSyncExcludedTable: () => isSyncExcludedTable,
15787
- getServiceDbPath: () => getServiceDbPath,
15788
- discoverSyncableServices: () => discoverSyncableServices,
15789
- discoverServices: () => discoverServices,
15790
- SYNC_EXCLUDED_TABLE_PATTERNS: () => SYNC_EXCLUDED_TABLE_PATTERNS,
15791
- KNOWN_PG_SERVICES: () => KNOWN_PG_SERVICES
16315
+ var exports_discover2 = {};
16316
+ __export(exports_discover2, {
16317
+ isSyncExcludedTable: () => isSyncExcludedTable2,
16318
+ getServiceDbPath: () => getServiceDbPath2,
16319
+ discoverSyncableServices: () => discoverSyncableServices2,
16320
+ discoverServices: () => discoverServices2,
16321
+ SYNC_EXCLUDED_TABLE_PATTERNS: () => SYNC_EXCLUDED_TABLE_PATTERNS2,
16322
+ KNOWN_PG_SERVICES: () => KNOWN_PG_SERVICES2
15792
16323
  });
15793
- import { readdirSync as readdirSync3, existsSync as existsSync5 } from "fs";
15794
- import { join as join5 } from "path";
15795
- import { homedir as homedir5 } from "os";
15796
- function isSyncExcludedTable(table) {
15797
- return SYNC_EXCLUDED_TABLE_PATTERNS.some((p) => p.test(table));
16324
+ import { readdirSync as readdirSync4, existsSync as existsSync7 } from "fs";
16325
+ import { join as join7 } from "path";
16326
+ import { homedir as homedir7 } from "os";
16327
+ function isSyncExcludedTable2(table) {
16328
+ return SYNC_EXCLUDED_TABLE_PATTERNS2.some((p) => p.test(table));
15798
16329
  }
15799
- function discoverServices() {
15800
- const dataDir = join5(homedir5(), ".hasna");
15801
- if (!existsSync5(dataDir))
16330
+ function discoverServices2() {
16331
+ const dataDir = join7(homedir7(), ".hasna");
16332
+ if (!existsSync7(dataDir))
15802
16333
  return [];
15803
16334
  try {
15804
- const entries = readdirSync3(dataDir, { withFileTypes: true });
16335
+ const entries = readdirSync4(dataDir, { withFileTypes: true });
15805
16336
  return entries.filter((e) => {
15806
16337
  if (!e.isDirectory())
15807
16338
  return false;
@@ -15813,37 +16344,37 @@ function discoverServices() {
15813
16344
  return [];
15814
16345
  }
15815
16346
  }
15816
- function discoverSyncableServices() {
15817
- const local = discoverServices();
15818
- const pgSet = new Set(KNOWN_PG_SERVICES);
16347
+ function discoverSyncableServices2() {
16348
+ const local = discoverServices2();
16349
+ const pgSet = new Set(KNOWN_PG_SERVICES2);
15819
16350
  return local.filter((s) => pgSet.has(s));
15820
16351
  }
15821
- function getServiceDbPath(service) {
15822
- const dataDir = join5(homedir5(), ".hasna", service);
15823
- if (!existsSync5(dataDir))
16352
+ function getServiceDbPath2(service) {
16353
+ const dataDir = join7(homedir7(), ".hasna", service);
16354
+ if (!existsSync7(dataDir))
15824
16355
  return null;
15825
16356
  const candidates = [
15826
- join5(dataDir, `${service}.db`),
15827
- join5(dataDir, "data.db"),
15828
- join5(dataDir, "database.db")
16357
+ join7(dataDir, `${service}.db`),
16358
+ join7(dataDir, "data.db"),
16359
+ join7(dataDir, "database.db")
15829
16360
  ];
15830
16361
  try {
15831
- const files = readdirSync3(dataDir);
16362
+ const files = readdirSync4(dataDir);
15832
16363
  for (const f of files) {
15833
16364
  if (f.endsWith(".db") && !f.endsWith("-wal") && !f.endsWith("-shm")) {
15834
- candidates.push(join5(dataDir, f));
16365
+ candidates.push(join7(dataDir, f));
15835
16366
  }
15836
16367
  }
15837
16368
  } catch {}
15838
16369
  for (const p of candidates) {
15839
- if (existsSync5(p))
16370
+ if (existsSync7(p))
15840
16371
  return p;
15841
16372
  }
15842
16373
  return null;
15843
16374
  }
15844
- var KNOWN_PG_SERVICES, SYNC_EXCLUDED_TABLE_PATTERNS;
15845
- var init_discover = __esm(() => {
15846
- KNOWN_PG_SERVICES = [
16375
+ var KNOWN_PG_SERVICES2, SYNC_EXCLUDED_TABLE_PATTERNS2;
16376
+ var init_discover2 = __esm(() => {
16377
+ KNOWN_PG_SERVICES2 = [
15847
16378
  "assistants",
15848
16379
  "attachments",
15849
16380
  "brains",
@@ -15880,116 +16411,7 @@ var init_discover = __esm(() => {
15880
16411
  "todos",
15881
16412
  "wallets"
15882
16413
  ];
15883
- SYNC_EXCLUDED_TABLE_PATTERNS = [
15884
- /^sqlite_/,
15885
- /_fts$/,
15886
- /_fts_/,
15887
- /^_sync_/,
15888
- /^_pg_migrations$/
15889
- ];
15890
- });
15891
-
15892
- // src/discover.ts
15893
- var exports_discover2 = {};
15894
- __export(exports_discover2, {
15895
- isSyncExcludedTable: () => isSyncExcludedTable2,
15896
- getServiceDbPath: () => getServiceDbPath2,
15897
- discoverSyncableServices: () => discoverSyncableServices2,
15898
- discoverServices: () => discoverServices2,
15899
- SYNC_EXCLUDED_TABLE_PATTERNS: () => SYNC_EXCLUDED_TABLE_PATTERNS2,
15900
- KNOWN_PG_SERVICES: () => KNOWN_PG_SERVICES2
15901
- });
15902
- import { readdirSync as readdirSync4, existsSync as existsSync6 } from "fs";
15903
- import { join as join6 } from "path";
15904
- import { homedir as homedir6 } from "os";
15905
- function isSyncExcludedTable2(table) {
15906
- return SYNC_EXCLUDED_TABLE_PATTERNS2.some((p) => p.test(table));
15907
- }
15908
- function discoverServices2() {
15909
- const dataDir = join6(homedir6(), ".hasna");
15910
- if (!existsSync6(dataDir))
15911
- return [];
15912
- try {
15913
- const entries = readdirSync4(dataDir, { withFileTypes: true });
15914
- return entries.filter((e) => {
15915
- if (!e.isDirectory())
15916
- return false;
15917
- if (e.name === "cloud" || e.name.startsWith("."))
15918
- return false;
15919
- return true;
15920
- }).map((e) => e.name).sort();
15921
- } catch {
15922
- return [];
15923
- }
15924
- }
15925
- function discoverSyncableServices2() {
15926
- const local = discoverServices2();
15927
- const pgSet = new Set(KNOWN_PG_SERVICES2);
15928
- return local.filter((s) => pgSet.has(s));
15929
- }
15930
- function getServiceDbPath2(service) {
15931
- const dataDir = join6(homedir6(), ".hasna", service);
15932
- if (!existsSync6(dataDir))
15933
- return null;
15934
- const candidates = [
15935
- join6(dataDir, `${service}.db`),
15936
- join6(dataDir, "data.db"),
15937
- join6(dataDir, "database.db")
15938
- ];
15939
- try {
15940
- const files = readdirSync4(dataDir);
15941
- for (const f of files) {
15942
- if (f.endsWith(".db") && !f.endsWith("-wal") && !f.endsWith("-shm")) {
15943
- candidates.push(join6(dataDir, f));
15944
- }
15945
- }
15946
- } catch {}
15947
- for (const p of candidates) {
15948
- if (existsSync6(p))
15949
- return p;
15950
- }
15951
- return null;
15952
- }
15953
- var KNOWN_PG_SERVICES2, SYNC_EXCLUDED_TABLE_PATTERNS2;
15954
- var init_discover2 = __esm(() => {
15955
- KNOWN_PG_SERVICES2 = [
15956
- "assistants",
15957
- "attachments",
15958
- "brains",
15959
- "configs",
15960
- "connectors",
15961
- "contacts",
15962
- "context",
15963
- "conversations",
15964
- "crawl",
15965
- "deployment",
15966
- "economy",
15967
- "emails",
15968
- "files",
15969
- "hooks",
15970
- "implementations",
15971
- "logs",
15972
- "mcps",
15973
- "mementos",
15974
- "microservices",
15975
- "predictor",
15976
- "prompts",
15977
- "recordings",
15978
- "researcher",
15979
- "sandboxes",
15980
- "search",
15981
- "secrets",
15982
- "sessions",
15983
- "signatures",
15984
- "skills",
15985
- "telephony",
15986
- "terminal",
15987
- "testers",
15988
- "tickets",
15989
- "todos",
15990
- "wallets"
15991
- ];
15992
- SYNC_EXCLUDED_TABLE_PATTERNS2 = [
16414
+ SYNC_EXCLUDED_TABLE_PATTERNS2 = [
15993
16415
  /^sqlite_/,
15994
16416
  /_fts$/,
15995
16417
  /_fts_/,
@@ -16081,7 +16503,7 @@ async function migrateService(service, connectionString) {
16081
16503
  return applyPgMigrations(connStr, migrations, service);
16082
16504
  }
16083
16505
  async function migrateAllServices() {
16084
- const { discoverServices: discoverServices3 } = await Promise.resolve().then(() => (init_discover2(), exports_discover2));
16506
+ const { discoverServices: discoverServices3 } = await Promise.resolve().then(() => (init_discover(), exports_discover));
16085
16507
  const services = discoverServices3();
16086
16508
  const results = [];
16087
16509
  for (const service of services) {
@@ -16121,7 +16543,7 @@ async function ensurePgDatabase(service) {
16121
16543
  }
16122
16544
  }
16123
16545
  async function ensureAllPgDatabases() {
16124
- const { discoverServices: discoverServices3 } = await Promise.resolve().then(() => (init_discover2(), exports_discover2));
16546
+ const { discoverServices: discoverServices3 } = await Promise.resolve().then(() => (init_discover(), exports_discover));
16125
16547
  const services = discoverServices3();
16126
16548
  const results = [];
16127
16549
  for (const service of services) {
@@ -20781,6 +21203,7 @@ config(en_default2());
20781
21203
 
20782
21204
  // node_modules/@modelcontextprotocol/sdk/dist/esm/types.js
20783
21205
  var LATEST_PROTOCOL_VERSION = "2025-11-25";
21206
+ var DEFAULT_NEGOTIATED_PROTOCOL_VERSION = "2025-03-26";
20784
21207
  var SUPPORTED_PROTOCOL_VERSIONS = [LATEST_PROTOCOL_VERSION, "2025-06-18", "2025-03-26", "2024-11-05", "2024-10-07"];
20785
21208
  var RELATED_TASK_META_KEY = "io.modelcontextprotocol/related-task";
20786
21209
  var JSONRPC_VERSION = "2.0";
@@ -20953,6 +21376,7 @@ var InitializeRequestSchema = RequestSchema.extend({
20953
21376
  method: literal("initialize"),
20954
21377
  params: InitializeRequestParamsSchema
20955
21378
  });
21379
+ var isInitializeRequest = (value) => InitializeRequestSchema.safeParse(value).success;
20956
21380
  var ServerCapabilitiesSchema = object2({
20957
21381
  experimental: record(string2(), AssertObjectSchema).optional(),
20958
21382
  logging: AssertObjectSchema.optional(),
@@ -25133,9 +25557,25 @@ init_external();
25133
25557
  init_zod();
25134
25558
  init_adapter();
25135
25559
  init_dotfile();
25136
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "fs";
25137
- import { homedir as homedir2 } from "os";
25138
- import { join as join2 } from "path";
25560
+ init_machines();
25561
+ import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "fs";
25562
+ import { homedir as homedir4 } from "os";
25563
+ import { join as join4 } from "path";
25564
+ var DaemonConfigSchema = exports_external.object({
25565
+ enabled: exports_external.boolean().default(false),
25566
+ paused: exports_external.boolean().default(false),
25567
+ watch_interval_seconds: exports_external.number().int().positive().default(5),
25568
+ pull_interval_seconds: exports_external.number().int().positive().default(60),
25569
+ push_debounce_seconds: exports_external.number().int().positive().default(5),
25570
+ conflict_strategy: exports_external.enum(["newest-wins", "local-wins", "remote-wins"]).default("newest-wins"),
25571
+ services: exports_external.array(exports_external.string()).default([]),
25572
+ table_intervals: exports_external.record(exports_external.string(), exports_external.record(exports_external.string(), exports_external.number().int().positive())).default({}),
25573
+ file_rules: exports_external.array(exports_external.object({
25574
+ path: exports_external.string(),
25575
+ interval_seconds: exports_external.number().int().positive().default(30),
25576
+ enabled: exports_external.boolean().default(true)
25577
+ })).default([])
25578
+ }).default({});
25139
25579
  var CloudConfigSchema = exports_external.object({
25140
25580
  rds: exports_external.object({
25141
25581
  host: exports_external.string().default(""),
@@ -25149,12 +25589,13 @@ var CloudConfigSchema = exports_external.object({
25149
25589
  feedback_endpoint: exports_external.string().default("https://feedback.hasna.com/api/v1/feedback"),
25150
25590
  sync: exports_external.object({
25151
25591
  schedule_minutes: exports_external.number().default(0)
25152
- }).default({})
25592
+ }).default({}),
25593
+ daemon: DaemonConfigSchema
25153
25594
  });
25154
- var CONFIG_DIR = join2(homedir2(), ".hasna", "cloud");
25155
- var CONFIG_PATH = join2(CONFIG_DIR, "config.json");
25595
+ var CONFIG_DIR = join4(homedir4(), ".hasna", "cloud");
25596
+ var CONFIG_PATH = join4(CONFIG_DIR, "config.json");
25156
25597
  function getCloudConfig() {
25157
- if (!existsSync2(CONFIG_PATH)) {
25598
+ if (!existsSync4(CONFIG_PATH)) {
25158
25599
  return CloudConfigSchema.parse({});
25159
25600
  }
25160
25601
  try {
@@ -25182,10 +25623,10 @@ function createDatabase(options) {
25182
25623
  const mode = options.mode ?? config2.mode;
25183
25624
  if (mode === "cloud") {
25184
25625
  const connStr = options.pgConnectionString ?? getConnectionString(options.service);
25185
- return new PgAdapter(connStr);
25626
+ return createMachineAwareAdapter(new PgAdapter(connStr));
25186
25627
  }
25187
25628
  const dbPath = options.sqlitePath ?? getDbPath(options.service);
25188
- return new SqliteAdapter(dbPath);
25629
+ return createMachineAwareAdapter(new SqliteAdapter(dbPath));
25189
25630
  }
25190
25631
 
25191
25632
  // src/sync.ts
@@ -25364,6 +25805,9 @@ async function ensureTablesExist(source, target, tables) {
25364
25805
  }
25365
25806
  async function filterColumnsForTarget(target, table, sourceColumns) {
25366
25807
  try {
25808
+ if (sourceColumns.includes("machine_id") && table !== "machines") {
25809
+ await ensureMachineIdColumnInTarget(target, table);
25810
+ }
25367
25811
  if (!isAsyncAdapter(target)) {
25368
25812
  const colInfo = target.all(`PRAGMA table_info("${table}")`);
25369
25813
  if (Array.isArray(colInfo) && colInfo.length > 0) {
@@ -25386,6 +25830,22 @@ async function filterColumnsForTarget(target, table, sourceColumns) {
25386
25830
  } catch {}
25387
25831
  return sourceColumns;
25388
25832
  }
25833
+ async function ensureMachineIdColumnInTarget(target, table) {
25834
+ if (!isAsyncAdapter(target)) {
25835
+ const colInfo2 = target.all(`PRAGMA table_info("${table}")`);
25836
+ const hasMachineId = Array.isArray(colInfo2) ? colInfo2.some((column) => column.name === "machine_id") : false;
25837
+ if (!hasMachineId) {
25838
+ target.exec(`ALTER TABLE "${table}" ADD COLUMN machine_id TEXT DEFAULT ''`);
25839
+ }
25840
+ return;
25841
+ }
25842
+ const colInfo = await target.all(`SELECT column_name
25843
+ FROM information_schema.columns
25844
+ WHERE table_schema = 'public' AND table_name = '${table}' AND column_name = 'machine_id'`);
25845
+ if (colInfo.length === 0) {
25846
+ await target.exec(`ALTER TABLE "${table}" ADD COLUMN machine_id TEXT DEFAULT ''`);
25847
+ }
25848
+ }
25389
25849
  async function syncTransfer(source, target, options, _direction) {
25390
25850
  const {
25391
25851
  tables,
@@ -25621,7 +26081,7 @@ async function listPgTables(db) {
25621
26081
 
25622
26082
  // src/feedback.ts
25623
26083
  init_config();
25624
- import { hostname as hostname2 } from "os";
26084
+ import { hostname as hostname3 } from "os";
25625
26085
  var FEEDBACK_TABLE_SQL = `
25626
26086
  CREATE TABLE IF NOT EXISTS feedback (
25627
26087
  id TEXT PRIMARY KEY,
@@ -25639,7 +26099,7 @@ function saveFeedback(db, feedback) {
25639
26099
  ensureFeedbackTable(db);
25640
26100
  const id = feedback.id ?? Math.random().toString(36).slice(2) + Date.now().toString(36);
25641
26101
  const now = new Date().toISOString();
25642
- const machineId = feedback.machine_id ?? hostname2();
26102
+ const machineId = feedback.machine_id ?? hostname3();
25643
26103
  db.run(`INSERT INTO feedback (id, service, version, message, email, machine_id, created_at)
25644
26104
  VALUES (?, ?, ?, ?, ?, ?, ?)`, id, feedback.service, feedback.version ?? "", feedback.message, feedback.email ?? "", machineId, feedback.created_at ?? now);
25645
26105
  return id;
@@ -25647,7 +26107,7 @@ function saveFeedback(db, feedback) {
25647
26107
  async function sendFeedback(feedback, db) {
25648
26108
  const config2 = getCloudConfig2();
25649
26109
  const id = feedback.id ?? Math.random().toString(36).slice(2) + Date.now().toString(36);
25650
- const machineId = feedback.machine_id ?? hostname2();
26110
+ const machineId = feedback.machine_id ?? hostname3();
25651
26111
  const now = new Date().toISOString();
25652
26112
  const payload = {
25653
26113
  id,
@@ -25687,22 +26147,22 @@ async function sendFeedback(feedback, db) {
25687
26147
 
25688
26148
  // src/dotfile.ts
25689
26149
  import {
25690
- existsSync as existsSync4,
26150
+ existsSync as existsSync6,
25691
26151
  mkdirSync as mkdirSync4,
25692
- readdirSync as readdirSync2,
26152
+ readdirSync as readdirSync3,
25693
26153
  copyFileSync as copyFileSync2
25694
26154
  } from "fs";
25695
- import { homedir as homedir4 } from "os";
25696
- import { join as join4, relative as relative2 } from "path";
25697
- var HASNA_DIR2 = join4(homedir4(), ".hasna");
26155
+ import { homedir as homedir6 } from "os";
26156
+ import { join as join6, relative as relative2 } from "path";
26157
+ var HASNA_DIR2 = join6(homedir6(), ".hasna");
25698
26158
  function getDataDir2(serviceName) {
25699
- const dir = join4(HASNA_DIR2, serviceName);
26159
+ const dir = join6(HASNA_DIR2, serviceName);
25700
26160
  mkdirSync4(dir, { recursive: true });
25701
26161
  return dir;
25702
26162
  }
25703
26163
  function getDbPath2(serviceName) {
25704
26164
  const dir = getDataDir2(serviceName);
25705
- return join4(dir, `${serviceName}.db`);
26165
+ return join6(dir, `${serviceName}.db`);
25706
26166
  }
25707
26167
 
25708
26168
  // src/adapter.ts
@@ -25824,282 +26284,1509 @@ class PgAdapterAsync2 {
25824
26284
  }
25825
26285
  }
25826
26286
 
25827
- // src/mcp/index.ts
25828
- var server = new McpServer({
25829
- name: "cloud",
25830
- version: "0.1.0"
25831
- });
25832
- server.tool("cloud_status", "Show cloud configuration and connection health", {}, async () => {
25833
- const config2 = getCloudConfig();
25834
- const lines = [
25835
- `Mode: ${config2.mode}`,
25836
- `RDS Host: ${config2.rds.host || "(not configured)"}`,
25837
- `RDS Port: ${config2.rds.port}`,
25838
- `RDS Username: ${config2.rds.username || "(not configured)"}`,
25839
- `SSL: ${config2.rds.ssl}`,
25840
- `Auto-sync: ${config2.auto_sync_interval_minutes ? `${config2.auto_sync_interval_minutes} min` : "disabled"}`
25841
- ];
25842
- if (config2.rds.host && config2.rds.username) {
25843
- try {
25844
- const connStr = getConnectionString("postgres");
25845
- const pg2 = new PgAdapterAsync2(connStr);
25846
- await pg2.get("SELECT 1 as ok");
25847
- lines.push("PostgreSQL: connected");
25848
- await pg2.close();
25849
- } catch (err) {
25850
- lines.push(`PostgreSQL: connection failed \u2014 ${err?.message}`);
25851
- }
26287
+ // src/mcp/http.ts
26288
+ import { createServer } from "node:http";
26289
+
26290
+ // node_modules/@hono/node-server/dist/index.mjs
26291
+ import { Http2ServerRequest as Http2ServerRequest2 } from "http2";
26292
+ import { Http2ServerRequest } from "http2";
26293
+ import { Readable } from "stream";
26294
+ import crypto2 from "crypto";
26295
+ var RequestError = class extends Error {
26296
+ constructor(message, options) {
26297
+ super(message, options);
26298
+ this.name = "RequestError";
25852
26299
  }
25853
- return { content: [{ type: "text", text: lines.join(`
25854
- `) }] };
25855
- });
25856
- server.tool("sync_push", "Push local SQLite data to cloud PostgreSQL", {
25857
- service: exports_external.string().describe("Service name"),
25858
- tables: exports_external.string().optional().describe("Comma-separated table names (default: all)")
25859
- }, async ({ service, tables: tablesStr }) => {
25860
- const config2 = getCloudConfig();
25861
- if (config2.mode === "local") {
25862
- return {
25863
- content: [
25864
- {
25865
- type: "text",
25866
- text: "Error: mode is 'local'. Configure cloud mode first via `cloud setup`."
25867
- }
25868
- ],
25869
- isError: true
25870
- };
26300
+ };
26301
+ var toRequestError = (e) => {
26302
+ if (e instanceof RequestError) {
26303
+ return e;
25871
26304
  }
25872
- const dbPath = getDbPath2(service);
25873
- const local = new SqliteAdapter2(dbPath);
25874
- const connStr = getConnectionString(service);
25875
- const cloud = new PgAdapterAsync2(connStr);
25876
- let tableList;
25877
- if (tablesStr) {
25878
- tableList = tablesStr.split(",").map((t) => t.trim());
25879
- } else {
25880
- tableList = listSqliteTables(local);
25881
- }
25882
- const results = await syncPush(local, cloud, { tables: tableList });
25883
- local.close();
25884
- await cloud.close();
25885
- const totalWritten = results.reduce((s, r) => s + r.rowsWritten, 0);
25886
- const totalErrors = results.reduce((s, r) => s + r.errors.length, 0);
25887
- const lines = [
25888
- `Pushed ${tableList.length} table(s): ${totalWritten} rows, ${totalErrors} errors.`
25889
- ];
25890
- for (const r of results) {
25891
- lines.push(` ${r.table}: ${r.rowsWritten} written, ${r.rowsSkipped} skipped`);
25892
- for (const e of r.errors) {
25893
- lines.push(` ERROR: ${e}`);
26305
+ return new RequestError(e.message, { cause: e });
26306
+ };
26307
+ var GlobalRequest = global.Request;
26308
+ var Request = class extends GlobalRequest {
26309
+ constructor(input, options) {
26310
+ if (typeof input === "object" && getRequestCache in input) {
26311
+ input = input[getRequestCache]();
25894
26312
  }
26313
+ if (typeof options?.body?.getReader !== "undefined") {
26314
+ options.duplex ??= "half";
26315
+ }
26316
+ super(input, options);
25895
26317
  }
25896
- return { content: [{ type: "text", text: lines.join(`
25897
- `) }] };
25898
- });
25899
- server.tool("sync_pull", "Pull cloud PostgreSQL data to local SQLite", {
25900
- service: exports_external.string().describe("Service name"),
25901
- tables: exports_external.string().optional().describe("Comma-separated table names (default: all)")
25902
- }, async ({ service, tables: tablesStr }) => {
25903
- const config2 = getCloudConfig();
25904
- if (config2.mode === "local") {
25905
- return {
25906
- content: [
25907
- {
25908
- type: "text",
25909
- text: "Error: mode is 'local'. Configure cloud mode first via `cloud setup`."
26318
+ };
26319
+ var newHeadersFromIncoming = (incoming) => {
26320
+ const headerRecord = [];
26321
+ const rawHeaders = incoming.rawHeaders;
26322
+ for (let i = 0;i < rawHeaders.length; i += 2) {
26323
+ const { [i]: key, [i + 1]: value } = rawHeaders;
26324
+ if (key.charCodeAt(0) !== 58) {
26325
+ headerRecord.push([key, value]);
26326
+ }
26327
+ }
26328
+ return new Headers(headerRecord);
26329
+ };
26330
+ var wrapBodyStream = Symbol("wrapBodyStream");
26331
+ var newRequestFromIncoming = (method, url, headers, incoming, abortController) => {
26332
+ const init = {
26333
+ method,
26334
+ headers,
26335
+ signal: abortController.signal
26336
+ };
26337
+ if (method === "TRACE") {
26338
+ init.method = "GET";
26339
+ const req = new Request(url, init);
26340
+ Object.defineProperty(req, "method", {
26341
+ get() {
26342
+ return "TRACE";
26343
+ }
26344
+ });
26345
+ return req;
26346
+ }
26347
+ if (!(method === "GET" || method === "HEAD")) {
26348
+ if ("rawBody" in incoming && incoming.rawBody instanceof Buffer) {
26349
+ init.body = new ReadableStream({
26350
+ start(controller) {
26351
+ controller.enqueue(incoming.rawBody);
26352
+ controller.close();
25910
26353
  }
25911
- ],
25912
- isError: true
25913
- };
26354
+ });
26355
+ } else if (incoming[wrapBodyStream]) {
26356
+ let reader;
26357
+ init.body = new ReadableStream({
26358
+ async pull(controller) {
26359
+ try {
26360
+ reader ||= Readable.toWeb(incoming).getReader();
26361
+ const { done, value } = await reader.read();
26362
+ if (done) {
26363
+ controller.close();
26364
+ } else {
26365
+ controller.enqueue(value);
26366
+ }
26367
+ } catch (error2) {
26368
+ controller.error(error2);
26369
+ }
26370
+ }
26371
+ });
26372
+ } else {
26373
+ init.body = Readable.toWeb(incoming);
26374
+ }
25914
26375
  }
25915
- const dbPath = getDbPath2(service);
25916
- const local = new SqliteAdapter2(dbPath);
25917
- const connStr = getConnectionString(service);
25918
- const cloud = new PgAdapterAsync2(connStr);
25919
- let tableList;
25920
- if (tablesStr) {
25921
- tableList = tablesStr.split(",").map((t) => t.trim());
25922
- } else {
26376
+ return new Request(url, init);
26377
+ };
26378
+ var getRequestCache = Symbol("getRequestCache");
26379
+ var requestCache = Symbol("requestCache");
26380
+ var incomingKey = Symbol("incomingKey");
26381
+ var urlKey = Symbol("urlKey");
26382
+ var headersKey = Symbol("headersKey");
26383
+ var abortControllerKey = Symbol("abortControllerKey");
26384
+ var getAbortController = Symbol("getAbortController");
26385
+ var requestPrototype = {
26386
+ get method() {
26387
+ return this[incomingKey].method || "GET";
26388
+ },
26389
+ get url() {
26390
+ return this[urlKey];
26391
+ },
26392
+ get headers() {
26393
+ return this[headersKey] ||= newHeadersFromIncoming(this[incomingKey]);
26394
+ },
26395
+ [getAbortController]() {
26396
+ this[getRequestCache]();
26397
+ return this[abortControllerKey];
26398
+ },
26399
+ [getRequestCache]() {
26400
+ this[abortControllerKey] ||= new AbortController;
26401
+ return this[requestCache] ||= newRequestFromIncoming(this.method, this[urlKey], this.headers, this[incomingKey], this[abortControllerKey]);
26402
+ }
26403
+ };
26404
+ [
26405
+ "body",
26406
+ "bodyUsed",
26407
+ "cache",
26408
+ "credentials",
26409
+ "destination",
26410
+ "integrity",
26411
+ "mode",
26412
+ "redirect",
26413
+ "referrer",
26414
+ "referrerPolicy",
26415
+ "signal",
26416
+ "keepalive"
26417
+ ].forEach((k) => {
26418
+ Object.defineProperty(requestPrototype, k, {
26419
+ get() {
26420
+ return this[getRequestCache]()[k];
26421
+ }
26422
+ });
26423
+ });
26424
+ ["arrayBuffer", "blob", "clone", "formData", "json", "text"].forEach((k) => {
26425
+ Object.defineProperty(requestPrototype, k, {
26426
+ value: function() {
26427
+ return this[getRequestCache]()[k]();
26428
+ }
26429
+ });
26430
+ });
26431
+ Object.setPrototypeOf(requestPrototype, Request.prototype);
26432
+ var newRequest = (incoming, defaultHostname) => {
26433
+ const req = Object.create(requestPrototype);
26434
+ req[incomingKey] = incoming;
26435
+ const incomingUrl = incoming.url || "";
26436
+ if (incomingUrl[0] !== "/" && (incomingUrl.startsWith("http://") || incomingUrl.startsWith("https://"))) {
26437
+ if (incoming instanceof Http2ServerRequest) {
26438
+ throw new RequestError("Absolute URL for :path is not allowed in HTTP/2");
26439
+ }
25923
26440
  try {
25924
- tableList = await listPgTables(cloud);
25925
- } catch {
25926
- local.close();
25927
- await cloud.close();
25928
- return {
25929
- content: [
25930
- { type: "text", text: "Error: failed to list tables from cloud." }
25931
- ],
25932
- isError: true
25933
- };
26441
+ const url2 = new URL(incomingUrl);
26442
+ req[urlKey] = url2.href;
26443
+ } catch (e) {
26444
+ throw new RequestError("Invalid absolute URL", { cause: e });
25934
26445
  }
26446
+ return req;
25935
26447
  }
25936
- const results = await syncPull(cloud, local, { tables: tableList });
25937
- local.close();
25938
- await cloud.close();
25939
- const totalWritten = results.reduce((s, r) => s + r.rowsWritten, 0);
25940
- const totalErrors = results.reduce((s, r) => s + r.errors.length, 0);
25941
- const lines = [
25942
- `Pulled ${tableList.length} table(s): ${totalWritten} rows, ${totalErrors} errors.`
25943
- ];
25944
- for (const r of results) {
25945
- lines.push(` ${r.table}: ${r.rowsWritten} written, ${r.rowsSkipped} skipped`);
25946
- for (const e of r.errors) {
25947
- lines.push(` ERROR: ${e}`);
26448
+ const host = (incoming instanceof Http2ServerRequest ? incoming.authority : incoming.headers.host) || defaultHostname;
26449
+ if (!host) {
26450
+ throw new RequestError("Missing host header");
26451
+ }
26452
+ let scheme;
26453
+ if (incoming instanceof Http2ServerRequest) {
26454
+ scheme = incoming.scheme;
26455
+ if (!(scheme === "http" || scheme === "https")) {
26456
+ throw new RequestError("Unsupported scheme");
25948
26457
  }
26458
+ } else {
26459
+ scheme = incoming.socket && incoming.socket.encrypted ? "https" : "http";
25949
26460
  }
25950
- return { content: [{ type: "text", text: lines.join(`
25951
- `) }] };
25952
- });
25953
- server.tool("send_feedback", "Send feedback for a service", {
25954
- service: exports_external.string().describe("Service name"),
25955
- message: exports_external.string().describe("Feedback message"),
25956
- email: exports_external.string().optional().describe("Contact email"),
25957
- version: exports_external.string().optional().describe("Service version")
25958
- }, async ({ service, message, email: email2, version: version2 }) => {
25959
- const db = createDatabase({ service: "cloud" });
25960
- const result = await sendFeedback({ service, message, email: email2, version: version2 }, db);
25961
- db.close();
25962
- if (result.sent) {
25963
- return {
25964
- content: [
25965
- {
25966
- type: "text",
25967
- text: `Feedback sent successfully (id: ${result.id})`
25968
- }
25969
- ]
25970
- };
26461
+ const url = new URL(`${scheme}://${host}${incomingUrl}`);
26462
+ if (url.hostname.length !== host.length && url.hostname !== host.replace(/:\d+$/, "")) {
26463
+ throw new RequestError("Invalid host header");
25971
26464
  }
25972
- return {
25973
- content: [
25974
- {
25975
- type: "text",
25976
- text: `Feedback saved locally (id: ${result.id}). Remote send failed: ${result.error}`
26465
+ req[urlKey] = url.href;
26466
+ return req;
26467
+ };
26468
+ var responseCache = Symbol("responseCache");
26469
+ var getResponseCache = Symbol("getResponseCache");
26470
+ var cacheKey = Symbol("cache");
26471
+ var GlobalResponse = global.Response;
26472
+ var Response2 = class _Response {
26473
+ #body;
26474
+ #init;
26475
+ [getResponseCache]() {
26476
+ delete this[cacheKey];
26477
+ return this[responseCache] ||= new GlobalResponse(this.#body, this.#init);
26478
+ }
26479
+ constructor(body, init) {
26480
+ let headers;
26481
+ this.#body = body;
26482
+ if (init instanceof _Response) {
26483
+ const cachedGlobalResponse = init[responseCache];
26484
+ if (cachedGlobalResponse) {
26485
+ this.#init = cachedGlobalResponse;
26486
+ this[getResponseCache]();
26487
+ return;
26488
+ } else {
26489
+ this.#init = init.#init;
26490
+ headers = new Headers(init.#init.headers);
25977
26491
  }
25978
- ]
25979
- };
26492
+ } else {
26493
+ this.#init = init;
26494
+ }
26495
+ if (typeof body === "string" || typeof body?.getReader !== "undefined" || body instanceof Blob || body instanceof Uint8Array) {
26496
+ this[cacheKey] = [init?.status || 200, body, headers || init?.headers];
26497
+ }
26498
+ }
26499
+ get headers() {
26500
+ const cache = this[cacheKey];
26501
+ if (cache) {
26502
+ if (!(cache[2] instanceof Headers)) {
26503
+ cache[2] = new Headers(cache[2] || { "content-type": "text/plain; charset=UTF-8" });
26504
+ }
26505
+ return cache[2];
26506
+ }
26507
+ return this[getResponseCache]().headers;
26508
+ }
26509
+ get status() {
26510
+ return this[cacheKey]?.[0] ?? this[getResponseCache]().status;
26511
+ }
26512
+ get ok() {
26513
+ const status = this.status;
26514
+ return status >= 200 && status < 300;
26515
+ }
26516
+ };
26517
+ ["body", "bodyUsed", "redirected", "statusText", "trailers", "type", "url"].forEach((k) => {
26518
+ Object.defineProperty(Response2.prototype, k, {
26519
+ get() {
26520
+ return this[getResponseCache]()[k];
26521
+ }
26522
+ });
25980
26523
  });
25981
- server.tool("sync_all", "Sync all discovered services between local and cloud. Pulls from PG to SQLite by default.", {
25982
- direction: exports_external.enum(["pull", "push"]).default("pull").describe("Sync direction")
25983
- }, async ({ direction }) => {
25984
- const config2 = getCloudConfig();
25985
- if (config2.mode === "local") {
25986
- return {
25987
- content: [{ type: "text", text: "Error: mode is 'local'. Configure cloud mode first via `cloud setup`." }],
25988
- isError: true
25989
- };
26524
+ ["arrayBuffer", "blob", "clone", "formData", "json", "text"].forEach((k) => {
26525
+ Object.defineProperty(Response2.prototype, k, {
26526
+ value: function() {
26527
+ return this[getResponseCache]()[k]();
26528
+ }
26529
+ });
26530
+ });
26531
+ Object.setPrototypeOf(Response2, GlobalResponse);
26532
+ Object.setPrototypeOf(Response2.prototype, GlobalResponse.prototype);
26533
+ async function readWithoutBlocking(readPromise) {
26534
+ return Promise.race([readPromise, Promise.resolve().then(() => Promise.resolve(undefined))]);
26535
+ }
26536
+ function writeFromReadableStreamDefaultReader(reader, writable, currentReadPromise) {
26537
+ const cancel = (error2) => {
26538
+ reader.cancel(error2).catch(() => {});
26539
+ };
26540
+ writable.on("close", cancel);
26541
+ writable.on("error", cancel);
26542
+ (currentReadPromise ?? reader.read()).then(flow, handleStreamError);
26543
+ return reader.closed.finally(() => {
26544
+ writable.off("close", cancel);
26545
+ writable.off("error", cancel);
26546
+ });
26547
+ function handleStreamError(error2) {
26548
+ if (error2) {
26549
+ writable.destroy(error2);
26550
+ }
25990
26551
  }
25991
- const { discoverServices: discoverServices3, isSyncExcludedTable: isSyncExcludedTable3 } = await Promise.resolve().then(() => (init_discover(), exports_discover));
25992
- const services = discoverServices3();
25993
- const lines = [`Syncing ${services.length} services (${direction})...`];
25994
- let grandTotal = 0;
25995
- let grandErrors = 0;
25996
- for (const service of services) {
26552
+ function onDrain() {
26553
+ reader.read().then(flow, handleStreamError);
26554
+ }
26555
+ function flow({ done, value }) {
25997
26556
  try {
25998
- const dbPath = getDbPath2(service);
25999
- const local = new SqliteAdapter2(dbPath);
26000
- const connStr = getConnectionString(service);
26001
- const cloud = new PgAdapterAsync2(connStr);
26002
- let tableList;
26003
- if (direction === "push") {
26004
- tableList = listSqliteTables(local).filter((t) => !isSyncExcludedTable3(t));
26557
+ if (done) {
26558
+ writable.end();
26559
+ } else if (!writable.write(value)) {
26560
+ writable.once("drain", onDrain);
26005
26561
  } else {
26006
- try {
26007
- tableList = (await listPgTables(cloud)).filter((t) => !isSyncExcludedTable3(t));
26008
- } catch {
26009
- local.close();
26010
- await cloud.close();
26011
- continue;
26012
- }
26013
- }
26014
- if (tableList.length === 0) {
26015
- local.close();
26016
- await cloud.close();
26017
- continue;
26562
+ return reader.read().then(flow, handleStreamError);
26018
26563
  }
26019
- const results = direction === "push" ? await syncPush(local, cloud, { tables: tableList }) : await syncPull(cloud, local, { tables: tableList });
26020
- local.close();
26021
- await cloud.close();
26022
- const written = results.reduce((s, r) => s + r.rowsWritten, 0);
26023
- const errors4 = results.reduce((s, r) => s + r.errors.length, 0);
26024
- grandTotal += written;
26025
- grandErrors += errors4;
26026
- if (written > 0 || errors4 > 0) {
26027
- lines.push(` ${service}: ${written} rows${errors4 > 0 ? `, ${errors4} errors` : ""}`);
26028
- }
26029
- } catch (err) {
26030
- grandErrors++;
26031
- lines.push(` ${service}: ERROR \u2014 ${err?.message ?? String(err)}`);
26564
+ } catch (e) {
26565
+ handleStreamError(e);
26032
26566
  }
26033
26567
  }
26034
- lines.push(`
26035
- Done. ${services.length} services, ${grandTotal} rows, ${grandErrors} errors.`);
26036
- return { content: [{ type: "text", text: lines.join(`
26037
- `) }] };
26038
- });
26039
- server.tool("migrate_all", "Run PG migrations for all discovered services. Creates databases if needed.", {}, async () => {
26040
- const { migrateAllServices: migrateAllServices2, ensureAllPgDatabases: ensureAllPgDatabases2 } = await Promise.resolve().then(() => (init_pg_migrate(), exports_pg_migrate));
26041
- const lines = ["Running PG migrations..."];
26042
- const dbResults = await ensureAllPgDatabases2();
26043
- for (const r of dbResults) {
26044
- if (r.created)
26045
- lines.push(` Created DB: ${r.service}`);
26046
- if (r.error)
26047
- lines.push(` DB error: ${r.service} \u2014 ${r.error}`);
26048
- }
26049
- const results = await migrateAllServices2();
26050
- let totalApplied = 0;
26051
- for (const r of results) {
26052
- totalApplied += r.applied.length;
26053
- if (r.applied.length > 0 || r.errors.length > 0) {
26054
- lines.push(` ${r.service}: ${r.applied.length} applied${r.errors.length > 0 ? `, ${r.errors.length} errors` : ""}`);
26055
- for (const e of r.errors)
26056
- lines.push(` ${e}`);
26057
- }
26058
- }
26059
- lines.push(`
26060
- Done. ${results.length} services, ${totalApplied} migrations applied.`);
26061
- return { content: [{ type: "text", text: lines.join(`
26062
- `) }] };
26568
+ }
26569
+ function writeFromReadableStream(stream, writable) {
26570
+ if (stream.locked) {
26571
+ throw new TypeError("ReadableStream is locked.");
26572
+ } else if (writable.destroyed) {
26573
+ return;
26574
+ }
26575
+ return writeFromReadableStreamDefaultReader(stream.getReader(), writable);
26576
+ }
26577
+ var buildOutgoingHttpHeaders = (headers) => {
26578
+ const res = {};
26579
+ if (!(headers instanceof Headers)) {
26580
+ headers = new Headers(headers ?? undefined);
26581
+ }
26582
+ const cookies = [];
26583
+ for (const [k, v] of headers) {
26584
+ if (k === "set-cookie") {
26585
+ cookies.push(v);
26586
+ } else {
26587
+ res[k] = v;
26588
+ }
26589
+ }
26590
+ if (cookies.length > 0) {
26591
+ res["set-cookie"] = cookies;
26592
+ }
26593
+ res["content-type"] ??= "text/plain; charset=UTF-8";
26594
+ return res;
26595
+ };
26596
+ var X_ALREADY_SENT = "x-hono-already-sent";
26597
+ if (typeof global.crypto === "undefined") {
26598
+ global.crypto = crypto2;
26599
+ }
26600
+ var outgoingEnded = Symbol("outgoingEnded");
26601
+ var handleRequestError = () => new Response(null, {
26602
+ status: 400
26063
26603
  });
26064
- var mcpAgentRegistry = new Map;
26065
- server.tool("register_agent", "Register an agent session for attribution", {
26066
- name: exports_external.string().describe("Agent name"),
26067
- session_id: exports_external.string().optional().describe("Session identifier")
26068
- }, async ({ name, session_id }) => {
26069
- const existing = [...mcpAgentRegistry.values()].find((a) => a.name === name);
26070
- if (existing) {
26071
- existing.last_seen_at = new Date().toISOString();
26072
- return { content: [{ type: "text", text: JSON.stringify({ agent_id: existing.id, name: existing.name, last_seen_at: existing.last_seen_at }) }] };
26073
- }
26074
- const id = Math.random().toString(36).slice(2, 10);
26075
- const agent = { id, name, last_seen_at: new Date().toISOString() };
26076
- mcpAgentRegistry.set(id, agent);
26077
- return { content: [{ type: "text", text: JSON.stringify(agent) }] };
26078
- });
26079
- server.tool("heartbeat", "Update agent last_seen_at", {
26080
- agent_id: exports_external.string().optional().describe("Agent ID (optional \u2014 updates by name if registered)")
26081
- }, async () => {
26082
- return { content: [{ type: "text", text: `heartbeat at ${new Date().toISOString()}` }] };
26083
- });
26084
- server.tool("list_agents", "List registered agents", {}, async () => {
26085
- const agents = [...mcpAgentRegistry.values()];
26086
- return { content: [{ type: "text", text: agents.length > 0 ? JSON.stringify(agents) : "No agents registered" }] };
26087
- });
26088
- server.tool("set_focus", "Set active project context", {
26089
- agent_id: exports_external.string().describe("Agent ID"),
26090
- project_id: exports_external.string().optional().describe("Project to focus on")
26091
- }, async ({ agent_id, project_id }) => {
26092
- const agent = mcpAgentRegistry.get(agent_id);
26093
- if (!agent)
26094
- return { content: [{ type: "text", text: `Agent not found: ${agent_id}` }], isError: true };
26095
- agent.project_id = project_id;
26096
- return { content: [{ type: "text", text: project_id ? `Focus set: ${project_id}` : "Focus cleared" }] };
26604
+ var handleFetchError = (e) => new Response(null, {
26605
+ status: e instanceof Error && (e.name === "TimeoutError" || e.constructor.name === "TimeoutError") ? 504 : 500
26097
26606
  });
26098
- async function main() {
26099
- const transport = new StdioServerTransport;
26607
+ var handleResponseError = (e, outgoing) => {
26608
+ const err = e instanceof Error ? e : new Error("unknown error", { cause: e });
26609
+ if (err.code === "ERR_STREAM_PREMATURE_CLOSE") {
26610
+ console.info("The user aborted a request.");
26611
+ } else {
26612
+ console.error(e);
26613
+ if (!outgoing.headersSent) {
26614
+ outgoing.writeHead(500, { "Content-Type": "text/plain" });
26615
+ }
26616
+ outgoing.end(`Error: ${err.message}`);
26617
+ outgoing.destroy(err);
26618
+ }
26619
+ };
26620
+ var flushHeaders = (outgoing) => {
26621
+ if ("flushHeaders" in outgoing && outgoing.writable) {
26622
+ outgoing.flushHeaders();
26623
+ }
26624
+ };
26625
+ var responseViaCache = async (res, outgoing) => {
26626
+ let [status, body, header] = res[cacheKey];
26627
+ let hasContentLength = false;
26628
+ if (!header) {
26629
+ header = { "content-type": "text/plain; charset=UTF-8" };
26630
+ } else if (header instanceof Headers) {
26631
+ hasContentLength = header.has("content-length");
26632
+ header = buildOutgoingHttpHeaders(header);
26633
+ } else if (Array.isArray(header)) {
26634
+ const headerObj = new Headers(header);
26635
+ hasContentLength = headerObj.has("content-length");
26636
+ header = buildOutgoingHttpHeaders(headerObj);
26637
+ } else {
26638
+ for (const key in header) {
26639
+ if (key.length === 14 && key.toLowerCase() === "content-length") {
26640
+ hasContentLength = true;
26641
+ break;
26642
+ }
26643
+ }
26644
+ }
26645
+ if (!hasContentLength) {
26646
+ if (typeof body === "string") {
26647
+ header["Content-Length"] = Buffer.byteLength(body);
26648
+ } else if (body instanceof Uint8Array) {
26649
+ header["Content-Length"] = body.byteLength;
26650
+ } else if (body instanceof Blob) {
26651
+ header["Content-Length"] = body.size;
26652
+ }
26653
+ }
26654
+ outgoing.writeHead(status, header);
26655
+ if (typeof body === "string" || body instanceof Uint8Array) {
26656
+ outgoing.end(body);
26657
+ } else if (body instanceof Blob) {
26658
+ outgoing.end(new Uint8Array(await body.arrayBuffer()));
26659
+ } else {
26660
+ flushHeaders(outgoing);
26661
+ await writeFromReadableStream(body, outgoing)?.catch((e) => handleResponseError(e, outgoing));
26662
+ }
26663
+ outgoing[outgoingEnded]?.();
26664
+ };
26665
+ var isPromise = (res) => typeof res.then === "function";
26666
+ var responseViaResponseObject = async (res, outgoing, options = {}) => {
26667
+ if (isPromise(res)) {
26668
+ if (options.errorHandler) {
26669
+ try {
26670
+ res = await res;
26671
+ } catch (err) {
26672
+ const errRes = await options.errorHandler(err);
26673
+ if (!errRes) {
26674
+ return;
26675
+ }
26676
+ res = errRes;
26677
+ }
26678
+ } else {
26679
+ res = await res.catch(handleFetchError);
26680
+ }
26681
+ }
26682
+ if (cacheKey in res) {
26683
+ return responseViaCache(res, outgoing);
26684
+ }
26685
+ const resHeaderRecord = buildOutgoingHttpHeaders(res.headers);
26686
+ if (res.body) {
26687
+ const reader = res.body.getReader();
26688
+ const values = [];
26689
+ let done = false;
26690
+ let currentReadPromise = undefined;
26691
+ if (resHeaderRecord["transfer-encoding"] !== "chunked") {
26692
+ let maxReadCount = 2;
26693
+ for (let i = 0;i < maxReadCount; i++) {
26694
+ currentReadPromise ||= reader.read();
26695
+ const chunk = await readWithoutBlocking(currentReadPromise).catch((e) => {
26696
+ console.error(e);
26697
+ done = true;
26698
+ });
26699
+ if (!chunk) {
26700
+ if (i === 1) {
26701
+ await new Promise((resolve) => setTimeout(resolve));
26702
+ maxReadCount = 3;
26703
+ continue;
26704
+ }
26705
+ break;
26706
+ }
26707
+ currentReadPromise = undefined;
26708
+ if (chunk.value) {
26709
+ values.push(chunk.value);
26710
+ }
26711
+ if (chunk.done) {
26712
+ done = true;
26713
+ break;
26714
+ }
26715
+ }
26716
+ if (done && !("content-length" in resHeaderRecord)) {
26717
+ resHeaderRecord["content-length"] = values.reduce((acc, value) => acc + value.length, 0);
26718
+ }
26719
+ }
26720
+ outgoing.writeHead(res.status, resHeaderRecord);
26721
+ values.forEach((value) => {
26722
+ outgoing.write(value);
26723
+ });
26724
+ if (done) {
26725
+ outgoing.end();
26726
+ } else {
26727
+ if (values.length === 0) {
26728
+ flushHeaders(outgoing);
26729
+ }
26730
+ await writeFromReadableStreamDefaultReader(reader, outgoing, currentReadPromise);
26731
+ }
26732
+ } else if (resHeaderRecord[X_ALREADY_SENT]) {} else {
26733
+ outgoing.writeHead(res.status, resHeaderRecord);
26734
+ outgoing.end();
26735
+ }
26736
+ outgoing[outgoingEnded]?.();
26737
+ };
26738
+ var getRequestListener = (fetchCallback, options = {}) => {
26739
+ const autoCleanupIncoming = options.autoCleanupIncoming ?? true;
26740
+ if (options.overrideGlobalObjects !== false && global.Request !== Request) {
26741
+ Object.defineProperty(global, "Request", {
26742
+ value: Request
26743
+ });
26744
+ Object.defineProperty(global, "Response", {
26745
+ value: Response2
26746
+ });
26747
+ }
26748
+ return async (incoming, outgoing) => {
26749
+ let res, req;
26750
+ try {
26751
+ req = newRequest(incoming, options.hostname);
26752
+ let incomingEnded = !autoCleanupIncoming || incoming.method === "GET" || incoming.method === "HEAD";
26753
+ if (!incomingEnded) {
26754
+ incoming[wrapBodyStream] = true;
26755
+ incoming.on("end", () => {
26756
+ incomingEnded = true;
26757
+ });
26758
+ if (incoming instanceof Http2ServerRequest2) {
26759
+ outgoing[outgoingEnded] = () => {
26760
+ if (!incomingEnded) {
26761
+ setTimeout(() => {
26762
+ if (!incomingEnded) {
26763
+ setTimeout(() => {
26764
+ incoming.destroy();
26765
+ outgoing.destroy();
26766
+ });
26767
+ }
26768
+ });
26769
+ }
26770
+ };
26771
+ }
26772
+ }
26773
+ outgoing.on("close", () => {
26774
+ const abortController = req[abortControllerKey];
26775
+ if (abortController) {
26776
+ if (incoming.errored) {
26777
+ req[abortControllerKey].abort(incoming.errored.toString());
26778
+ } else if (!outgoing.writableFinished) {
26779
+ req[abortControllerKey].abort("Client connection prematurely closed.");
26780
+ }
26781
+ }
26782
+ if (!incomingEnded) {
26783
+ setTimeout(() => {
26784
+ if (!incomingEnded) {
26785
+ setTimeout(() => {
26786
+ incoming.destroy();
26787
+ });
26788
+ }
26789
+ });
26790
+ }
26791
+ });
26792
+ res = fetchCallback(req, { incoming, outgoing });
26793
+ if (cacheKey in res) {
26794
+ return responseViaCache(res, outgoing);
26795
+ }
26796
+ } catch (e) {
26797
+ if (!res) {
26798
+ if (options.errorHandler) {
26799
+ res = await options.errorHandler(req ? e : toRequestError(e));
26800
+ if (!res) {
26801
+ return;
26802
+ }
26803
+ } else if (!req) {
26804
+ res = handleRequestError();
26805
+ } else {
26806
+ res = handleFetchError(e);
26807
+ }
26808
+ } else {
26809
+ return handleResponseError(e, outgoing);
26810
+ }
26811
+ }
26812
+ try {
26813
+ return await responseViaResponseObject(res, outgoing, options);
26814
+ } catch (e) {
26815
+ return handleResponseError(e, outgoing);
26816
+ }
26817
+ };
26818
+ };
26819
+
26820
+ // node_modules/@modelcontextprotocol/sdk/dist/esm/server/webStandardStreamableHttp.js
26821
+ class WebStandardStreamableHTTPServerTransport {
26822
+ constructor(options = {}) {
26823
+ this._started = false;
26824
+ this._hasHandledRequest = false;
26825
+ this._streamMapping = new Map;
26826
+ this._requestToStreamMapping = new Map;
26827
+ this._requestResponseMap = new Map;
26828
+ this._initialized = false;
26829
+ this._enableJsonResponse = false;
26830
+ this._standaloneSseStreamId = "_GET_stream";
26831
+ this.sessionIdGenerator = options.sessionIdGenerator;
26832
+ this._enableJsonResponse = options.enableJsonResponse ?? false;
26833
+ this._eventStore = options.eventStore;
26834
+ this._onsessioninitialized = options.onsessioninitialized;
26835
+ this._onsessionclosed = options.onsessionclosed;
26836
+ this._allowedHosts = options.allowedHosts;
26837
+ this._allowedOrigins = options.allowedOrigins;
26838
+ this._enableDnsRebindingProtection = options.enableDnsRebindingProtection ?? false;
26839
+ this._retryInterval = options.retryInterval;
26840
+ }
26841
+ async start() {
26842
+ if (this._started) {
26843
+ throw new Error("Transport already started");
26844
+ }
26845
+ this._started = true;
26846
+ }
26847
+ createJsonErrorResponse(status, code, message, options) {
26848
+ const error2 = { code, message };
26849
+ if (options?.data !== undefined) {
26850
+ error2.data = options.data;
26851
+ }
26852
+ return new Response(JSON.stringify({
26853
+ jsonrpc: "2.0",
26854
+ error: error2,
26855
+ id: null
26856
+ }), {
26857
+ status,
26858
+ headers: {
26859
+ "Content-Type": "application/json",
26860
+ ...options?.headers
26861
+ }
26862
+ });
26863
+ }
26864
+ validateRequestHeaders(req) {
26865
+ if (!this._enableDnsRebindingProtection) {
26866
+ return;
26867
+ }
26868
+ if (this._allowedHosts && this._allowedHosts.length > 0) {
26869
+ const hostHeader = req.headers.get("host");
26870
+ if (!hostHeader || !this._allowedHosts.includes(hostHeader)) {
26871
+ const error2 = `Invalid Host header: ${hostHeader}`;
26872
+ this.onerror?.(new Error(error2));
26873
+ return this.createJsonErrorResponse(403, -32000, error2);
26874
+ }
26875
+ }
26876
+ if (this._allowedOrigins && this._allowedOrigins.length > 0) {
26877
+ const originHeader = req.headers.get("origin");
26878
+ if (originHeader && !this._allowedOrigins.includes(originHeader)) {
26879
+ const error2 = `Invalid Origin header: ${originHeader}`;
26880
+ this.onerror?.(new Error(error2));
26881
+ return this.createJsonErrorResponse(403, -32000, error2);
26882
+ }
26883
+ }
26884
+ return;
26885
+ }
26886
+ async handleRequest(req, options) {
26887
+ if (!this.sessionIdGenerator && this._hasHandledRequest) {
26888
+ throw new Error("Stateless transport cannot be reused across requests. Create a new transport per request.");
26889
+ }
26890
+ this._hasHandledRequest = true;
26891
+ const validationError = this.validateRequestHeaders(req);
26892
+ if (validationError) {
26893
+ return validationError;
26894
+ }
26895
+ switch (req.method) {
26896
+ case "POST":
26897
+ return this.handlePostRequest(req, options);
26898
+ case "GET":
26899
+ return this.handleGetRequest(req);
26900
+ case "DELETE":
26901
+ return this.handleDeleteRequest(req);
26902
+ default:
26903
+ return this.handleUnsupportedRequest();
26904
+ }
26905
+ }
26906
+ async writePrimingEvent(controller, encoder, streamId, protocolVersion) {
26907
+ if (!this._eventStore) {
26908
+ return;
26909
+ }
26910
+ if (protocolVersion < "2025-11-25") {
26911
+ return;
26912
+ }
26913
+ const primingEventId = await this._eventStore.storeEvent(streamId, {});
26914
+ let primingEvent = `id: ${primingEventId}
26915
+ data:
26916
+
26917
+ `;
26918
+ if (this._retryInterval !== undefined) {
26919
+ primingEvent = `id: ${primingEventId}
26920
+ retry: ${this._retryInterval}
26921
+ data:
26922
+
26923
+ `;
26924
+ }
26925
+ controller.enqueue(encoder.encode(primingEvent));
26926
+ }
26927
+ async handleGetRequest(req) {
26928
+ const acceptHeader = req.headers.get("accept");
26929
+ if (!acceptHeader?.includes("text/event-stream")) {
26930
+ this.onerror?.(new Error("Not Acceptable: Client must accept text/event-stream"));
26931
+ return this.createJsonErrorResponse(406, -32000, "Not Acceptable: Client must accept text/event-stream");
26932
+ }
26933
+ const sessionError = this.validateSession(req);
26934
+ if (sessionError) {
26935
+ return sessionError;
26936
+ }
26937
+ const protocolError = this.validateProtocolVersion(req);
26938
+ if (protocolError) {
26939
+ return protocolError;
26940
+ }
26941
+ if (this._eventStore) {
26942
+ const lastEventId = req.headers.get("last-event-id");
26943
+ if (lastEventId) {
26944
+ return this.replayEvents(lastEventId);
26945
+ }
26946
+ }
26947
+ if (this._streamMapping.get(this._standaloneSseStreamId) !== undefined) {
26948
+ this.onerror?.(new Error("Conflict: Only one SSE stream is allowed per session"));
26949
+ return this.createJsonErrorResponse(409, -32000, "Conflict: Only one SSE stream is allowed per session");
26950
+ }
26951
+ const encoder = new TextEncoder;
26952
+ let streamController;
26953
+ const readable = new ReadableStream({
26954
+ start: (controller) => {
26955
+ streamController = controller;
26956
+ },
26957
+ cancel: () => {
26958
+ this._streamMapping.delete(this._standaloneSseStreamId);
26959
+ }
26960
+ });
26961
+ const headers = {
26962
+ "Content-Type": "text/event-stream",
26963
+ "Cache-Control": "no-cache, no-transform",
26964
+ Connection: "keep-alive"
26965
+ };
26966
+ if (this.sessionId !== undefined) {
26967
+ headers["mcp-session-id"] = this.sessionId;
26968
+ }
26969
+ this._streamMapping.set(this._standaloneSseStreamId, {
26970
+ controller: streamController,
26971
+ encoder,
26972
+ cleanup: () => {
26973
+ this._streamMapping.delete(this._standaloneSseStreamId);
26974
+ try {
26975
+ streamController.close();
26976
+ } catch {}
26977
+ }
26978
+ });
26979
+ return new Response(readable, { headers });
26980
+ }
26981
+ async replayEvents(lastEventId) {
26982
+ if (!this._eventStore) {
26983
+ this.onerror?.(new Error("Event store not configured"));
26984
+ return this.createJsonErrorResponse(400, -32000, "Event store not configured");
26985
+ }
26986
+ try {
26987
+ let streamId;
26988
+ if (this._eventStore.getStreamIdForEventId) {
26989
+ streamId = await this._eventStore.getStreamIdForEventId(lastEventId);
26990
+ if (!streamId) {
26991
+ this.onerror?.(new Error("Invalid event ID format"));
26992
+ return this.createJsonErrorResponse(400, -32000, "Invalid event ID format");
26993
+ }
26994
+ if (this._streamMapping.get(streamId) !== undefined) {
26995
+ this.onerror?.(new Error("Conflict: Stream already has an active connection"));
26996
+ return this.createJsonErrorResponse(409, -32000, "Conflict: Stream already has an active connection");
26997
+ }
26998
+ }
26999
+ const headers = {
27000
+ "Content-Type": "text/event-stream",
27001
+ "Cache-Control": "no-cache, no-transform",
27002
+ Connection: "keep-alive"
27003
+ };
27004
+ if (this.sessionId !== undefined) {
27005
+ headers["mcp-session-id"] = this.sessionId;
27006
+ }
27007
+ const encoder = new TextEncoder;
27008
+ let streamController;
27009
+ const readable = new ReadableStream({
27010
+ start: (controller) => {
27011
+ streamController = controller;
27012
+ },
27013
+ cancel: () => {}
27014
+ });
27015
+ const replayedStreamId = await this._eventStore.replayEventsAfter(lastEventId, {
27016
+ send: async (eventId, message) => {
27017
+ const success = this.writeSSEEvent(streamController, encoder, message, eventId);
27018
+ if (!success) {
27019
+ this.onerror?.(new Error("Failed replay events"));
27020
+ try {
27021
+ streamController.close();
27022
+ } catch {}
27023
+ }
27024
+ }
27025
+ });
27026
+ this._streamMapping.set(replayedStreamId, {
27027
+ controller: streamController,
27028
+ encoder,
27029
+ cleanup: () => {
27030
+ this._streamMapping.delete(replayedStreamId);
27031
+ try {
27032
+ streamController.close();
27033
+ } catch {}
27034
+ }
27035
+ });
27036
+ return new Response(readable, { headers });
27037
+ } catch (error2) {
27038
+ this.onerror?.(error2);
27039
+ return this.createJsonErrorResponse(500, -32000, "Error replaying events");
27040
+ }
27041
+ }
27042
+ writeSSEEvent(controller, encoder, message, eventId) {
27043
+ try {
27044
+ let eventData = `event: message
27045
+ `;
27046
+ if (eventId) {
27047
+ eventData += `id: ${eventId}
27048
+ `;
27049
+ }
27050
+ eventData += `data: ${JSON.stringify(message)}
27051
+
27052
+ `;
27053
+ controller.enqueue(encoder.encode(eventData));
27054
+ return true;
27055
+ } catch (error2) {
27056
+ this.onerror?.(error2);
27057
+ return false;
27058
+ }
27059
+ }
27060
+ handleUnsupportedRequest() {
27061
+ this.onerror?.(new Error("Method not allowed."));
27062
+ return new Response(JSON.stringify({
27063
+ jsonrpc: "2.0",
27064
+ error: {
27065
+ code: -32000,
27066
+ message: "Method not allowed."
27067
+ },
27068
+ id: null
27069
+ }), {
27070
+ status: 405,
27071
+ headers: {
27072
+ Allow: "GET, POST, DELETE",
27073
+ "Content-Type": "application/json"
27074
+ }
27075
+ });
27076
+ }
27077
+ async handlePostRequest(req, options) {
27078
+ try {
27079
+ const acceptHeader = req.headers.get("accept");
27080
+ if (!acceptHeader?.includes("application/json") || !acceptHeader.includes("text/event-stream")) {
27081
+ this.onerror?.(new Error("Not Acceptable: Client must accept both application/json and text/event-stream"));
27082
+ return this.createJsonErrorResponse(406, -32000, "Not Acceptable: Client must accept both application/json and text/event-stream");
27083
+ }
27084
+ const ct = req.headers.get("content-type");
27085
+ if (!ct || !ct.includes("application/json")) {
27086
+ this.onerror?.(new Error("Unsupported Media Type: Content-Type must be application/json"));
27087
+ return this.createJsonErrorResponse(415, -32000, "Unsupported Media Type: Content-Type must be application/json");
27088
+ }
27089
+ const requestInfo = {
27090
+ headers: Object.fromEntries(req.headers.entries()),
27091
+ url: new URL(req.url)
27092
+ };
27093
+ let rawMessage;
27094
+ if (options?.parsedBody !== undefined) {
27095
+ rawMessage = options.parsedBody;
27096
+ } else {
27097
+ try {
27098
+ rawMessage = await req.json();
27099
+ } catch {
27100
+ this.onerror?.(new Error("Parse error: Invalid JSON"));
27101
+ return this.createJsonErrorResponse(400, -32700, "Parse error: Invalid JSON");
27102
+ }
27103
+ }
27104
+ let messages;
27105
+ try {
27106
+ if (Array.isArray(rawMessage)) {
27107
+ messages = rawMessage.map((msg) => JSONRPCMessageSchema.parse(msg));
27108
+ } else {
27109
+ messages = [JSONRPCMessageSchema.parse(rawMessage)];
27110
+ }
27111
+ } catch {
27112
+ this.onerror?.(new Error("Parse error: Invalid JSON-RPC message"));
27113
+ return this.createJsonErrorResponse(400, -32700, "Parse error: Invalid JSON-RPC message");
27114
+ }
27115
+ const isInitializationRequest = messages.some(isInitializeRequest);
27116
+ if (isInitializationRequest) {
27117
+ if (this._initialized && this.sessionId !== undefined) {
27118
+ this.onerror?.(new Error("Invalid Request: Server already initialized"));
27119
+ return this.createJsonErrorResponse(400, -32600, "Invalid Request: Server already initialized");
27120
+ }
27121
+ if (messages.length > 1) {
27122
+ this.onerror?.(new Error("Invalid Request: Only one initialization request is allowed"));
27123
+ return this.createJsonErrorResponse(400, -32600, "Invalid Request: Only one initialization request is allowed");
27124
+ }
27125
+ this.sessionId = this.sessionIdGenerator?.();
27126
+ this._initialized = true;
27127
+ if (this.sessionId && this._onsessioninitialized) {
27128
+ await Promise.resolve(this._onsessioninitialized(this.sessionId));
27129
+ }
27130
+ }
27131
+ if (!isInitializationRequest) {
27132
+ const sessionError = this.validateSession(req);
27133
+ if (sessionError) {
27134
+ return sessionError;
27135
+ }
27136
+ const protocolError = this.validateProtocolVersion(req);
27137
+ if (protocolError) {
27138
+ return protocolError;
27139
+ }
27140
+ }
27141
+ const hasRequests = messages.some(isJSONRPCRequest);
27142
+ if (!hasRequests) {
27143
+ for (const message of messages) {
27144
+ this.onmessage?.(message, { authInfo: options?.authInfo, requestInfo });
27145
+ }
27146
+ return new Response(null, { status: 202 });
27147
+ }
27148
+ const streamId = crypto.randomUUID();
27149
+ const initRequest = messages.find((m) => isInitializeRequest(m));
27150
+ const clientProtocolVersion = initRequest ? initRequest.params.protocolVersion : req.headers.get("mcp-protocol-version") ?? DEFAULT_NEGOTIATED_PROTOCOL_VERSION;
27151
+ if (this._enableJsonResponse) {
27152
+ return new Promise((resolve) => {
27153
+ this._streamMapping.set(streamId, {
27154
+ resolveJson: resolve,
27155
+ cleanup: () => {
27156
+ this._streamMapping.delete(streamId);
27157
+ }
27158
+ });
27159
+ for (const message of messages) {
27160
+ if (isJSONRPCRequest(message)) {
27161
+ this._requestToStreamMapping.set(message.id, streamId);
27162
+ }
27163
+ }
27164
+ for (const message of messages) {
27165
+ this.onmessage?.(message, { authInfo: options?.authInfo, requestInfo });
27166
+ }
27167
+ });
27168
+ }
27169
+ const encoder = new TextEncoder;
27170
+ let streamController;
27171
+ const readable = new ReadableStream({
27172
+ start: (controller) => {
27173
+ streamController = controller;
27174
+ },
27175
+ cancel: () => {
27176
+ this._streamMapping.delete(streamId);
27177
+ }
27178
+ });
27179
+ const headers = {
27180
+ "Content-Type": "text/event-stream",
27181
+ "Cache-Control": "no-cache",
27182
+ Connection: "keep-alive"
27183
+ };
27184
+ if (this.sessionId !== undefined) {
27185
+ headers["mcp-session-id"] = this.sessionId;
27186
+ }
27187
+ for (const message of messages) {
27188
+ if (isJSONRPCRequest(message)) {
27189
+ this._streamMapping.set(streamId, {
27190
+ controller: streamController,
27191
+ encoder,
27192
+ cleanup: () => {
27193
+ this._streamMapping.delete(streamId);
27194
+ try {
27195
+ streamController.close();
27196
+ } catch {}
27197
+ }
27198
+ });
27199
+ this._requestToStreamMapping.set(message.id, streamId);
27200
+ }
27201
+ }
27202
+ await this.writePrimingEvent(streamController, encoder, streamId, clientProtocolVersion);
27203
+ for (const message of messages) {
27204
+ let closeSSEStream;
27205
+ let closeStandaloneSSEStream;
27206
+ if (isJSONRPCRequest(message) && this._eventStore && clientProtocolVersion >= "2025-11-25") {
27207
+ closeSSEStream = () => {
27208
+ this.closeSSEStream(message.id);
27209
+ };
27210
+ closeStandaloneSSEStream = () => {
27211
+ this.closeStandaloneSSEStream();
27212
+ };
27213
+ }
27214
+ this.onmessage?.(message, { authInfo: options?.authInfo, requestInfo, closeSSEStream, closeStandaloneSSEStream });
27215
+ }
27216
+ return new Response(readable, { status: 200, headers });
27217
+ } catch (error2) {
27218
+ this.onerror?.(error2);
27219
+ return this.createJsonErrorResponse(400, -32700, "Parse error", { data: String(error2) });
27220
+ }
27221
+ }
27222
+ async handleDeleteRequest(req) {
27223
+ const sessionError = this.validateSession(req);
27224
+ if (sessionError) {
27225
+ return sessionError;
27226
+ }
27227
+ const protocolError = this.validateProtocolVersion(req);
27228
+ if (protocolError) {
27229
+ return protocolError;
27230
+ }
27231
+ await Promise.resolve(this._onsessionclosed?.(this.sessionId));
27232
+ await this.close();
27233
+ return new Response(null, { status: 200 });
27234
+ }
27235
+ validateSession(req) {
27236
+ if (this.sessionIdGenerator === undefined) {
27237
+ return;
27238
+ }
27239
+ if (!this._initialized) {
27240
+ this.onerror?.(new Error("Bad Request: Server not initialized"));
27241
+ return this.createJsonErrorResponse(400, -32000, "Bad Request: Server not initialized");
27242
+ }
27243
+ const sessionId = req.headers.get("mcp-session-id");
27244
+ if (!sessionId) {
27245
+ this.onerror?.(new Error("Bad Request: Mcp-Session-Id header is required"));
27246
+ return this.createJsonErrorResponse(400, -32000, "Bad Request: Mcp-Session-Id header is required");
27247
+ }
27248
+ if (sessionId !== this.sessionId) {
27249
+ this.onerror?.(new Error("Session not found"));
27250
+ return this.createJsonErrorResponse(404, -32001, "Session not found");
27251
+ }
27252
+ return;
27253
+ }
27254
+ validateProtocolVersion(req) {
27255
+ const protocolVersion = req.headers.get("mcp-protocol-version");
27256
+ if (protocolVersion !== null && !SUPPORTED_PROTOCOL_VERSIONS.includes(protocolVersion)) {
27257
+ this.onerror?.(new Error(`Bad Request: Unsupported protocol version: ${protocolVersion}` + ` (supported versions: ${SUPPORTED_PROTOCOL_VERSIONS.join(", ")})`));
27258
+ return this.createJsonErrorResponse(400, -32000, `Bad Request: Unsupported protocol version: ${protocolVersion} (supported versions: ${SUPPORTED_PROTOCOL_VERSIONS.join(", ")})`);
27259
+ }
27260
+ return;
27261
+ }
27262
+ async close() {
27263
+ this._streamMapping.forEach(({ cleanup }) => {
27264
+ cleanup();
27265
+ });
27266
+ this._streamMapping.clear();
27267
+ this._requestResponseMap.clear();
27268
+ this.onclose?.();
27269
+ }
27270
+ closeSSEStream(requestId) {
27271
+ const streamId = this._requestToStreamMapping.get(requestId);
27272
+ if (!streamId)
27273
+ return;
27274
+ const stream = this._streamMapping.get(streamId);
27275
+ if (stream) {
27276
+ stream.cleanup();
27277
+ }
27278
+ }
27279
+ closeStandaloneSSEStream() {
27280
+ const stream = this._streamMapping.get(this._standaloneSseStreamId);
27281
+ if (stream) {
27282
+ stream.cleanup();
27283
+ }
27284
+ }
27285
+ async send(message, options) {
27286
+ let requestId = options?.relatedRequestId;
27287
+ if (isJSONRPCResultResponse(message) || isJSONRPCErrorResponse(message)) {
27288
+ requestId = message.id;
27289
+ }
27290
+ if (requestId === undefined) {
27291
+ if (isJSONRPCResultResponse(message) || isJSONRPCErrorResponse(message)) {
27292
+ throw new Error("Cannot send a response on a standalone SSE stream unless resuming a previous client request");
27293
+ }
27294
+ let eventId;
27295
+ if (this._eventStore) {
27296
+ eventId = await this._eventStore.storeEvent(this._standaloneSseStreamId, message);
27297
+ }
27298
+ const standaloneSse = this._streamMapping.get(this._standaloneSseStreamId);
27299
+ if (standaloneSse === undefined) {
27300
+ return;
27301
+ }
27302
+ if (standaloneSse.controller && standaloneSse.encoder) {
27303
+ this.writeSSEEvent(standaloneSse.controller, standaloneSse.encoder, message, eventId);
27304
+ }
27305
+ return;
27306
+ }
27307
+ const streamId = this._requestToStreamMapping.get(requestId);
27308
+ if (!streamId) {
27309
+ throw new Error(`No connection established for request ID: ${String(requestId)}`);
27310
+ }
27311
+ const stream = this._streamMapping.get(streamId);
27312
+ if (!this._enableJsonResponse && stream?.controller && stream?.encoder) {
27313
+ let eventId;
27314
+ if (this._eventStore) {
27315
+ eventId = await this._eventStore.storeEvent(streamId, message);
27316
+ }
27317
+ this.writeSSEEvent(stream.controller, stream.encoder, message, eventId);
27318
+ }
27319
+ if (isJSONRPCResultResponse(message) || isJSONRPCErrorResponse(message)) {
27320
+ this._requestResponseMap.set(requestId, message);
27321
+ const relatedIds = Array.from(this._requestToStreamMapping.entries()).filter(([_, sid]) => sid === streamId).map(([id]) => id);
27322
+ const allResponsesReady = relatedIds.every((id) => this._requestResponseMap.has(id));
27323
+ if (allResponsesReady) {
27324
+ if (!stream) {
27325
+ throw new Error(`No connection established for request ID: ${String(requestId)}`);
27326
+ }
27327
+ if (this._enableJsonResponse && stream.resolveJson) {
27328
+ const headers = {
27329
+ "Content-Type": "application/json"
27330
+ };
27331
+ if (this.sessionId !== undefined) {
27332
+ headers["mcp-session-id"] = this.sessionId;
27333
+ }
27334
+ const responses = relatedIds.map((id) => this._requestResponseMap.get(id));
27335
+ if (responses.length === 1) {
27336
+ stream.resolveJson(new Response(JSON.stringify(responses[0]), { status: 200, headers }));
27337
+ } else {
27338
+ stream.resolveJson(new Response(JSON.stringify(responses), { status: 200, headers }));
27339
+ }
27340
+ } else {
27341
+ stream.cleanup();
27342
+ }
27343
+ for (const id of relatedIds) {
27344
+ this._requestResponseMap.delete(id);
27345
+ this._requestToStreamMapping.delete(id);
27346
+ }
27347
+ }
27348
+ }
27349
+ }
27350
+ }
27351
+
27352
+ // node_modules/@modelcontextprotocol/sdk/dist/esm/server/streamableHttp.js
27353
+ class StreamableHTTPServerTransport {
27354
+ constructor(options = {}) {
27355
+ this._requestContext = new WeakMap;
27356
+ this._webStandardTransport = new WebStandardStreamableHTTPServerTransport(options);
27357
+ this._requestListener = getRequestListener(async (webRequest) => {
27358
+ const context = this._requestContext.get(webRequest);
27359
+ return this._webStandardTransport.handleRequest(webRequest, {
27360
+ authInfo: context?.authInfo,
27361
+ parsedBody: context?.parsedBody
27362
+ });
27363
+ }, { overrideGlobalObjects: false });
27364
+ }
27365
+ get sessionId() {
27366
+ return this._webStandardTransport.sessionId;
27367
+ }
27368
+ set onclose(handler) {
27369
+ this._webStandardTransport.onclose = handler;
27370
+ }
27371
+ get onclose() {
27372
+ return this._webStandardTransport.onclose;
27373
+ }
27374
+ set onerror(handler) {
27375
+ this._webStandardTransport.onerror = handler;
27376
+ }
27377
+ get onerror() {
27378
+ return this._webStandardTransport.onerror;
27379
+ }
27380
+ set onmessage(handler) {
27381
+ this._webStandardTransport.onmessage = handler;
27382
+ }
27383
+ get onmessage() {
27384
+ return this._webStandardTransport.onmessage;
27385
+ }
27386
+ async start() {
27387
+ return this._webStandardTransport.start();
27388
+ }
27389
+ async close() {
27390
+ return this._webStandardTransport.close();
27391
+ }
27392
+ async send(message, options) {
27393
+ return this._webStandardTransport.send(message, options);
27394
+ }
27395
+ async handleRequest(req, res, parsedBody) {
27396
+ const authInfo = req.auth;
27397
+ const handler = getRequestListener(async (webRequest) => {
27398
+ return this._webStandardTransport.handleRequest(webRequest, {
27399
+ authInfo,
27400
+ parsedBody
27401
+ });
27402
+ }, { overrideGlobalObjects: false });
27403
+ await handler(req, res);
27404
+ }
27405
+ closeSSEStream(requestId) {
27406
+ this._webStandardTransport.closeSSEStream(requestId);
27407
+ }
27408
+ closeStandaloneSSEStream() {
27409
+ this._webStandardTransport.closeStandaloneSSEStream();
27410
+ }
27411
+ }
27412
+
27413
+ // src/mcp/http.ts
27414
+ var DEFAULT_MCP_HTTP_PORT = 8804;
27415
+ var MCP_SERVICE_NAME = "cloud";
27416
+ function resolveMcpHttpPort(explicit) {
27417
+ if (explicit != null && !Number.isNaN(explicit))
27418
+ return explicit;
27419
+ const env = process.env.MCP_HTTP_PORT;
27420
+ if (env) {
27421
+ const parsed = parseInt(env, 10);
27422
+ if (!Number.isNaN(parsed))
27423
+ return parsed;
27424
+ }
27425
+ return DEFAULT_MCP_HTTP_PORT;
27426
+ }
27427
+ function isHttpMode(argv = process.argv) {
27428
+ return argv.includes("--http") || process.env.MCP_HTTP === "1";
27429
+ }
27430
+ function parseHttpArgv(argv = process.argv) {
27431
+ const http = isHttpMode(argv);
27432
+ let port;
27433
+ const portIdx = argv.indexOf("--port");
27434
+ if (portIdx !== -1 && argv[portIdx + 1]) {
27435
+ port = parseInt(argv[portIdx + 1], 10);
27436
+ }
27437
+ return { http, port };
27438
+ }
27439
+ async function readJsonBody(req) {
27440
+ const chunks = [];
27441
+ for await (const chunk of req)
27442
+ chunks.push(chunk);
27443
+ if (chunks.length === 0)
27444
+ return;
27445
+ const text = Buffer.concat(chunks).toString("utf8");
27446
+ return text ? JSON.parse(text) : undefined;
27447
+ }
27448
+ async function handleStatelessMcpNode(req, res, getServer = buildServer) {
27449
+ const server = await getServer();
27450
+ const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
27451
+ await server.connect(transport);
27452
+ const body = req.method === "POST" ? await readJsonBody(req) : undefined;
27453
+ await transport.handleRequest(req, res, body);
27454
+ res.on("close", () => {
27455
+ transport.close();
27456
+ server.close();
27457
+ });
27458
+ }
27459
+ function healthPayload(name = MCP_SERVICE_NAME) {
27460
+ return { status: "ok", name };
27461
+ }
27462
+ async function startMcpHttpServer(options = {}) {
27463
+ const port = options.port ?? resolveMcpHttpPort();
27464
+ const host = "127.0.0.1";
27465
+ const getServer = options.getServer ?? buildServer;
27466
+ const name = options.name ?? MCP_SERVICE_NAME;
27467
+ const httpServer = createServer(async (req, res) => {
27468
+ const url = new URL(req.url ?? "/", `http://${host}:${port}`);
27469
+ if (req.method === "GET" && url.pathname === "/health") {
27470
+ res.writeHead(200, { "Content-Type": "application/json" });
27471
+ res.end(JSON.stringify(healthPayload(name)));
27472
+ return;
27473
+ }
27474
+ if (url.pathname === "/mcp") {
27475
+ await handleStatelessMcpNode(req, res, getServer);
27476
+ return;
27477
+ }
27478
+ res.writeHead(404);
27479
+ res.end("Not found");
27480
+ });
27481
+ await new Promise((resolve, reject) => {
27482
+ httpServer.once("error", reject);
27483
+ httpServer.listen(port, host, () => resolve());
27484
+ });
27485
+ const address = httpServer.address();
27486
+ const boundPort = typeof address === "object" && address ? address.port : port;
27487
+ return {
27488
+ port: boundPort,
27489
+ close: () => new Promise((resolve, reject) => {
27490
+ httpServer.close((err) => err ? reject(err) : resolve());
27491
+ })
27492
+ };
27493
+ }
27494
+ async function runMcpHttpServer(options = {}) {
27495
+ const { port } = await startMcpHttpServer(options);
27496
+ console.error(`cloud-mcp listening on http://127.0.0.1:${port}/mcp`);
27497
+ await new Promise(() => {});
27498
+ }
27499
+
27500
+ // src/mcp/index.ts
27501
+ function buildServer() {
27502
+ const server = new McpServer({
27503
+ name: "cloud",
27504
+ version: "0.1.0"
27505
+ });
27506
+ server.tool("cloud_status", "Show cloud configuration and connection health", {}, async () => {
27507
+ const config2 = getCloudConfig();
27508
+ const lines = [
27509
+ `Mode: ${config2.mode}`,
27510
+ `RDS Host: ${config2.rds.host || "(not configured)"}`,
27511
+ `RDS Port: ${config2.rds.port}`,
27512
+ `RDS Username: ${config2.rds.username || "(not configured)"}`,
27513
+ `SSL: ${config2.rds.ssl}`,
27514
+ `Auto-sync: ${config2.auto_sync_interval_minutes ? `${config2.auto_sync_interval_minutes} min` : "disabled"}`
27515
+ ];
27516
+ if (config2.rds.host && config2.rds.username) {
27517
+ try {
27518
+ const connStr = getConnectionString("postgres");
27519
+ const pg2 = new PgAdapterAsync2(connStr);
27520
+ await pg2.get("SELECT 1 as ok");
27521
+ lines.push("PostgreSQL: connected");
27522
+ await pg2.close();
27523
+ } catch (err) {
27524
+ const message = err instanceof Error ? err.message : String(err);
27525
+ lines.push(`PostgreSQL: connection failed \u2014 ${message}`);
27526
+ }
27527
+ }
27528
+ return { content: [{ type: "text", text: lines.join(`
27529
+ `) }] };
27530
+ });
27531
+ server.tool("sync_push", "Push local SQLite data to cloud PostgreSQL", {
27532
+ service: exports_external.string().describe("Service name"),
27533
+ tables: exports_external.string().optional().describe("Comma-separated table names (default: all)")
27534
+ }, async ({ service, tables: tablesStr }) => {
27535
+ const config2 = getCloudConfig();
27536
+ if (config2.mode === "local") {
27537
+ return {
27538
+ content: [
27539
+ {
27540
+ type: "text",
27541
+ text: "Error: mode is 'local'. Configure cloud mode first via `cloud setup`."
27542
+ }
27543
+ ],
27544
+ isError: true
27545
+ };
27546
+ }
27547
+ const dbPath = getDbPath2(service);
27548
+ const local = new SqliteAdapter2(dbPath);
27549
+ const connStr = getConnectionString(service);
27550
+ const cloud = new PgAdapterAsync2(connStr);
27551
+ let tableList;
27552
+ if (tablesStr) {
27553
+ tableList = tablesStr.split(",").map((t) => t.trim());
27554
+ } else {
27555
+ tableList = listSqliteTables(local);
27556
+ }
27557
+ const results = await syncPush(local, cloud, { tables: tableList });
27558
+ local.close();
27559
+ await cloud.close();
27560
+ const totalWritten = results.reduce((s, r) => s + r.rowsWritten, 0);
27561
+ const totalErrors = results.reduce((s, r) => s + r.errors.length, 0);
27562
+ const lines = [
27563
+ `Pushed ${tableList.length} table(s): ${totalWritten} rows, ${totalErrors} errors.`
27564
+ ];
27565
+ for (const r of results) {
27566
+ lines.push(` ${r.table}: ${r.rowsWritten} written, ${r.rowsSkipped} skipped`);
27567
+ for (const e of r.errors) {
27568
+ lines.push(` ERROR: ${e}`);
27569
+ }
27570
+ }
27571
+ return { content: [{ type: "text", text: lines.join(`
27572
+ `) }] };
27573
+ });
27574
+ server.tool("sync_pull", "Pull cloud PostgreSQL data to local SQLite", {
27575
+ service: exports_external.string().describe("Service name"),
27576
+ tables: exports_external.string().optional().describe("Comma-separated table names (default: all)")
27577
+ }, async ({ service, tables: tablesStr }) => {
27578
+ const config2 = getCloudConfig();
27579
+ if (config2.mode === "local") {
27580
+ return {
27581
+ content: [
27582
+ {
27583
+ type: "text",
27584
+ text: "Error: mode is 'local'. Configure cloud mode first via `cloud setup`."
27585
+ }
27586
+ ],
27587
+ isError: true
27588
+ };
27589
+ }
27590
+ const dbPath = getDbPath2(service);
27591
+ const local = new SqliteAdapter2(dbPath);
27592
+ const connStr = getConnectionString(service);
27593
+ const cloud = new PgAdapterAsync2(connStr);
27594
+ let tableList;
27595
+ if (tablesStr) {
27596
+ tableList = tablesStr.split(",").map((t) => t.trim());
27597
+ } else {
27598
+ try {
27599
+ tableList = await listPgTables(cloud);
27600
+ } catch {
27601
+ local.close();
27602
+ await cloud.close();
27603
+ return {
27604
+ content: [
27605
+ { type: "text", text: "Error: failed to list tables from cloud." }
27606
+ ],
27607
+ isError: true
27608
+ };
27609
+ }
27610
+ }
27611
+ const results = await syncPull(cloud, local, { tables: tableList });
27612
+ local.close();
27613
+ await cloud.close();
27614
+ const totalWritten = results.reduce((s, r) => s + r.rowsWritten, 0);
27615
+ const totalErrors = results.reduce((s, r) => s + r.errors.length, 0);
27616
+ const lines = [
27617
+ `Pulled ${tableList.length} table(s): ${totalWritten} rows, ${totalErrors} errors.`
27618
+ ];
27619
+ for (const r of results) {
27620
+ lines.push(` ${r.table}: ${r.rowsWritten} written, ${r.rowsSkipped} skipped`);
27621
+ for (const e of r.errors) {
27622
+ lines.push(` ERROR: ${e}`);
27623
+ }
27624
+ }
27625
+ return { content: [{ type: "text", text: lines.join(`
27626
+ `) }] };
27627
+ });
27628
+ server.tool("send_feedback", "Send feedback for a service", {
27629
+ service: exports_external.string().describe("Service name"),
27630
+ message: exports_external.string().describe("Feedback message"),
27631
+ email: exports_external.string().optional().describe("Contact email"),
27632
+ version: exports_external.string().optional().describe("Service version")
27633
+ }, async ({ service, message, email: email2, version: version2 }) => {
27634
+ const db = createDatabase({ service: "cloud" });
27635
+ const result = await sendFeedback({ service, message, email: email2, version: version2 }, db);
27636
+ db.close();
27637
+ if (result.sent) {
27638
+ return {
27639
+ content: [
27640
+ {
27641
+ type: "text",
27642
+ text: `Feedback sent successfully (id: ${result.id})`
27643
+ }
27644
+ ]
27645
+ };
27646
+ }
27647
+ return {
27648
+ content: [
27649
+ {
27650
+ type: "text",
27651
+ text: `Feedback saved locally (id: ${result.id}). Remote send failed: ${result.error}`
27652
+ }
27653
+ ]
27654
+ };
27655
+ });
27656
+ server.tool("sync_all", "Sync all discovered services between local and cloud. Pulls from PG to SQLite by default.", {
27657
+ direction: exports_external.enum(["pull", "push"]).default("pull").describe("Sync direction")
27658
+ }, async ({ direction }) => {
27659
+ const config2 = getCloudConfig();
27660
+ if (config2.mode === "local") {
27661
+ return {
27662
+ content: [{ type: "text", text: "Error: mode is 'local'. Configure cloud mode first via `cloud setup`." }],
27663
+ isError: true
27664
+ };
27665
+ }
27666
+ const { discoverServices: discoverServices3, isSyncExcludedTable: isSyncExcludedTable3 } = await Promise.resolve().then(() => (init_discover2(), exports_discover2));
27667
+ const services = discoverServices3();
27668
+ const lines = [`Syncing ${services.length} services (${direction})...`];
27669
+ let grandTotal = 0;
27670
+ let grandErrors = 0;
27671
+ for (const service of services) {
27672
+ try {
27673
+ const dbPath = getDbPath2(service);
27674
+ const local = new SqliteAdapter2(dbPath);
27675
+ const connStr = getConnectionString(service);
27676
+ const cloud = new PgAdapterAsync2(connStr);
27677
+ let tableList;
27678
+ if (direction === "push") {
27679
+ tableList = listSqliteTables(local).filter((t) => !isSyncExcludedTable3(t));
27680
+ } else {
27681
+ try {
27682
+ tableList = (await listPgTables(cloud)).filter((t) => !isSyncExcludedTable3(t));
27683
+ } catch {
27684
+ local.close();
27685
+ await cloud.close();
27686
+ continue;
27687
+ }
27688
+ }
27689
+ if (tableList.length === 0) {
27690
+ local.close();
27691
+ await cloud.close();
27692
+ continue;
27693
+ }
27694
+ const results = direction === "push" ? await syncPush(local, cloud, { tables: tableList }) : await syncPull(cloud, local, { tables: tableList });
27695
+ local.close();
27696
+ await cloud.close();
27697
+ const written = results.reduce((s, r) => s + r.rowsWritten, 0);
27698
+ const errors4 = results.reduce((s, r) => s + r.errors.length, 0);
27699
+ grandTotal += written;
27700
+ grandErrors += errors4;
27701
+ if (written > 0 || errors4 > 0) {
27702
+ lines.push(` ${service}: ${written} rows${errors4 > 0 ? `, ${errors4} errors` : ""}`);
27703
+ }
27704
+ } catch (err) {
27705
+ grandErrors++;
27706
+ const message = err instanceof Error ? err.message : String(err);
27707
+ lines.push(` ${service}: ERROR \u2014 ${message}`);
27708
+ }
27709
+ }
27710
+ lines.push(`
27711
+ Done. ${services.length} services, ${grandTotal} rows, ${grandErrors} errors.`);
27712
+ return { content: [{ type: "text", text: lines.join(`
27713
+ `) }] };
27714
+ });
27715
+ server.tool("migrate_all", "Run PG migrations for all discovered services. Creates databases if needed.", {}, async () => {
27716
+ const { migrateAllServices: migrateAllServices2, ensureAllPgDatabases: ensureAllPgDatabases2 } = await Promise.resolve().then(() => (init_pg_migrate(), exports_pg_migrate));
27717
+ const lines = ["Running PG migrations..."];
27718
+ const dbResults = await ensureAllPgDatabases2();
27719
+ for (const r of dbResults) {
27720
+ if (r.created)
27721
+ lines.push(` Created DB: ${r.service}`);
27722
+ if (r.error)
27723
+ lines.push(` DB error: ${r.service} \u2014 ${r.error}`);
27724
+ }
27725
+ const results = await migrateAllServices2();
27726
+ let totalApplied = 0;
27727
+ for (const r of results) {
27728
+ totalApplied += r.applied.length;
27729
+ if (r.applied.length > 0 || r.errors.length > 0) {
27730
+ lines.push(` ${r.service}: ${r.applied.length} applied${r.errors.length > 0 ? `, ${r.errors.length} errors` : ""}`);
27731
+ for (const e of r.errors)
27732
+ lines.push(` ${e}`);
27733
+ }
27734
+ }
27735
+ lines.push(`
27736
+ Done. ${results.length} services, ${totalApplied} migrations applied.`);
27737
+ return { content: [{ type: "text", text: lines.join(`
27738
+ `) }] };
27739
+ });
27740
+ const mcpAgentRegistry = new Map;
27741
+ server.tool("register_agent", "Register an agent session for attribution", {
27742
+ name: exports_external.string().describe("Agent name"),
27743
+ session_id: exports_external.string().optional().describe("Session identifier")
27744
+ }, async ({ name }) => {
27745
+ const existing = [...mcpAgentRegistry.values()].find((a) => a.name === name);
27746
+ if (existing) {
27747
+ existing.last_seen_at = new Date().toISOString();
27748
+ return { content: [{ type: "text", text: JSON.stringify({ agent_id: existing.id, name: existing.name, last_seen_at: existing.last_seen_at }) }] };
27749
+ }
27750
+ const id = Math.random().toString(36).slice(2, 10);
27751
+ const agent = { id, name, last_seen_at: new Date().toISOString() };
27752
+ mcpAgentRegistry.set(id, agent);
27753
+ return { content: [{ type: "text", text: JSON.stringify(agent) }] };
27754
+ });
27755
+ server.tool("heartbeat", "Update agent last_seen_at", {
27756
+ agent_id: exports_external.string().optional().describe("Agent ID (optional \u2014 updates by name if registered)")
27757
+ }, async () => {
27758
+ return { content: [{ type: "text", text: `heartbeat at ${new Date().toISOString()}` }] };
27759
+ });
27760
+ server.tool("list_agents", "List registered agents", {}, async () => {
27761
+ const agents = [...mcpAgentRegistry.values()];
27762
+ return { content: [{ type: "text", text: agents.length > 0 ? JSON.stringify(agents) : "No agents registered" }] };
27763
+ });
27764
+ server.tool("set_focus", "Set active project context", {
27765
+ agent_id: exports_external.string().describe("Agent ID"),
27766
+ project_id: exports_external.string().optional().describe("Project to focus on")
27767
+ }, async ({ agent_id, project_id }) => {
27768
+ const agent = mcpAgentRegistry.get(agent_id);
27769
+ if (!agent)
27770
+ return { content: [{ type: "text", text: `Agent not found: ${agent_id}` }], isError: true };
27771
+ agent.project_id = project_id;
27772
+ return { content: [{ type: "text", text: project_id ? `Focus set: ${project_id}` : "Focus cleared" }] };
27773
+ });
27774
+ return server;
27775
+ }
27776
+ async function main() {
27777
+ const { http, port } = parseHttpArgv();
27778
+ if (http) {
27779
+ await runMcpHttpServer({ port: resolveMcpHttpPort(port) });
27780
+ return;
27781
+ }
27782
+ const server = buildServer();
27783
+ const transport = new StdioServerTransport;
26100
27784
  await server.connect(transport);
26101
27785
  }
26102
27786
  main().catch((err) => {
26103
27787
  console.error("cloud-mcp failed to start:", err);
26104
27788
  process.exit(1);
26105
27789
  });
27790
+ export {
27791
+ buildServer
27792
+ };