sorokeep 1.0.0

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 (275) hide show
  1. package/CONTRIBUTING.md +296 -0
  2. package/LICENSE +21 -0
  3. package/README.md +677 -0
  4. package/dist/alerts/alerts.d.ts +21 -0
  5. package/dist/alerts/alerts.d.ts.map +1 -0
  6. package/dist/alerts/alerts.js +18 -0
  7. package/dist/alerts/alerts.js.map +1 -0
  8. package/dist/alerts/discord.d.ts +3 -0
  9. package/dist/alerts/discord.d.ts.map +1 -0
  10. package/dist/alerts/discord.js +199 -0
  11. package/dist/alerts/discord.js.map +1 -0
  12. package/dist/alerts/dispatcher.d.ts +13 -0
  13. package/dist/alerts/dispatcher.d.ts.map +1 -0
  14. package/dist/alerts/dispatcher.js +91 -0
  15. package/dist/alerts/dispatcher.js.map +1 -0
  16. package/dist/alerts/keys.d.ts +24 -0
  17. package/dist/alerts/keys.d.ts.map +1 -0
  18. package/dist/alerts/keys.js +44 -0
  19. package/dist/alerts/keys.js.map +1 -0
  20. package/dist/alerts/pagerduty.d.ts +8 -0
  21. package/dist/alerts/pagerduty.d.ts.map +1 -0
  22. package/dist/alerts/pagerduty.js +164 -0
  23. package/dist/alerts/pagerduty.js.map +1 -0
  24. package/dist/alerts/resource.d.ts +23 -0
  25. package/dist/alerts/resource.d.ts.map +1 -0
  26. package/dist/alerts/resource.js +167 -0
  27. package/dist/alerts/resource.js.map +1 -0
  28. package/dist/alerts/slack.d.ts +7 -0
  29. package/dist/alerts/slack.d.ts.map +1 -0
  30. package/dist/alerts/slack.js +184 -0
  31. package/dist/alerts/slack.js.map +1 -0
  32. package/dist/alerts/telegram.d.ts +9 -0
  33. package/dist/alerts/telegram.d.ts.map +1 -0
  34. package/dist/alerts/telegram.js +133 -0
  35. package/dist/alerts/telegram.js.map +1 -0
  36. package/dist/alerts/templates.d.ts +109 -0
  37. package/dist/alerts/templates.d.ts.map +1 -0
  38. package/dist/alerts/templates.js +139 -0
  39. package/dist/alerts/templates.js.map +1 -0
  40. package/dist/alerts/types.d.ts +113 -0
  41. package/dist/alerts/types.d.ts.map +1 -0
  42. package/dist/alerts/types.js +87 -0
  43. package/dist/alerts/types.js.map +1 -0
  44. package/dist/alerts/webhook.d.ts +13 -0
  45. package/dist/alerts/webhook.d.ts.map +1 -0
  46. package/dist/alerts/webhook.js +56 -0
  47. package/dist/alerts/webhook.js.map +1 -0
  48. package/dist/cli/schemaFormatter.d.ts +11 -0
  49. package/dist/cli/schemaFormatter.d.ts.map +1 -0
  50. package/dist/cli/schemaFormatter.js +74 -0
  51. package/dist/cli/schemaFormatter.js.map +1 -0
  52. package/dist/commands/alerts.d.ts +3 -0
  53. package/dist/commands/alerts.d.ts.map +1 -0
  54. package/dist/commands/alerts.js +256 -0
  55. package/dist/commands/alerts.js.map +1 -0
  56. package/dist/commands/budget.d.ts +3 -0
  57. package/dist/commands/budget.d.ts.map +1 -0
  58. package/dist/commands/budget.js +47 -0
  59. package/dist/commands/budget.js.map +1 -0
  60. package/dist/commands/channels.d.ts +3 -0
  61. package/dist/commands/channels.d.ts.map +1 -0
  62. package/dist/commands/channels.js +84 -0
  63. package/dist/commands/channels.js.map +1 -0
  64. package/dist/commands/check.d.ts +3 -0
  65. package/dist/commands/check.d.ts.map +1 -0
  66. package/dist/commands/check.js +58 -0
  67. package/dist/commands/check.js.map +1 -0
  68. package/dist/commands/completion.d.ts +3 -0
  69. package/dist/commands/completion.d.ts.map +1 -0
  70. package/dist/commands/completion.js +36 -0
  71. package/dist/commands/completion.js.map +1 -0
  72. package/dist/commands/costs.d.ts +15 -0
  73. package/dist/commands/costs.d.ts.map +1 -0
  74. package/dist/commands/costs.js +280 -0
  75. package/dist/commands/costs.js.map +1 -0
  76. package/dist/commands/daemon.d.ts +3 -0
  77. package/dist/commands/daemon.d.ts.map +1 -0
  78. package/dist/commands/daemon.js +93 -0
  79. package/dist/commands/daemon.js.map +1 -0
  80. package/dist/commands/db.d.ts +3 -0
  81. package/dist/commands/db.d.ts.map +1 -0
  82. package/dist/commands/db.js +140 -0
  83. package/dist/commands/db.js.map +1 -0
  84. package/dist/commands/guard.d.ts +3 -0
  85. package/dist/commands/guard.d.ts.map +1 -0
  86. package/dist/commands/guard.js +181 -0
  87. package/dist/commands/guard.js.map +1 -0
  88. package/dist/commands/history.d.ts +3 -0
  89. package/dist/commands/history.d.ts.map +1 -0
  90. package/dist/commands/history.js +57 -0
  91. package/dist/commands/history.js.map +1 -0
  92. package/dist/commands/inspect.d.ts +3 -0
  93. package/dist/commands/inspect.d.ts.map +1 -0
  94. package/dist/commands/inspect.js +82 -0
  95. package/dist/commands/inspect.js.map +1 -0
  96. package/dist/commands/mcp.d.ts +3 -0
  97. package/dist/commands/mcp.d.ts.map +1 -0
  98. package/dist/commands/mcp.js +20 -0
  99. package/dist/commands/mcp.js.map +1 -0
  100. package/dist/commands/pause.d.ts +3 -0
  101. package/dist/commands/pause.d.ts.map +1 -0
  102. package/dist/commands/pause.js +32 -0
  103. package/dist/commands/pause.js.map +1 -0
  104. package/dist/commands/resources.d.ts +3 -0
  105. package/dist/commands/resources.d.ts.map +1 -0
  106. package/dist/commands/resources.js +95 -0
  107. package/dist/commands/resources.js.map +1 -0
  108. package/dist/commands/restore.d.ts +3 -0
  109. package/dist/commands/restore.d.ts.map +1 -0
  110. package/dist/commands/restore.js +92 -0
  111. package/dist/commands/restore.js.map +1 -0
  112. package/dist/commands/resume.d.ts +3 -0
  113. package/dist/commands/resume.d.ts.map +1 -0
  114. package/dist/commands/resume.js +31 -0
  115. package/dist/commands/resume.js.map +1 -0
  116. package/dist/commands/status.d.ts +3 -0
  117. package/dist/commands/status.d.ts.map +1 -0
  118. package/dist/commands/status.js +63 -0
  119. package/dist/commands/status.js.map +1 -0
  120. package/dist/commands/watch.d.ts +3 -0
  121. package/dist/commands/watch.d.ts.map +1 -0
  122. package/dist/commands/watch.js +163 -0
  123. package/dist/commands/watch.js.map +1 -0
  124. package/dist/core/aws_secrets.d.ts +13 -0
  125. package/dist/core/aws_secrets.d.ts.map +1 -0
  126. package/dist/core/aws_secrets.js +34 -0
  127. package/dist/core/aws_secrets.js.map +1 -0
  128. package/dist/core/budget.d.ts +10 -0
  129. package/dist/core/budget.d.ts.map +1 -0
  130. package/dist/core/budget.js +31 -0
  131. package/dist/core/budget.js.map +1 -0
  132. package/dist/core/channels.d.ts +67 -0
  133. package/dist/core/channels.d.ts.map +1 -0
  134. package/dist/core/channels.js +136 -0
  135. package/dist/core/channels.js.map +1 -0
  136. package/dist/core/check.d.ts +25 -0
  137. package/dist/core/check.d.ts.map +1 -0
  138. package/dist/core/check.js +85 -0
  139. package/dist/core/check.js.map +1 -0
  140. package/dist/core/completion.d.ts +9 -0
  141. package/dist/core/completion.d.ts.map +1 -0
  142. package/dist/core/completion.js +68 -0
  143. package/dist/core/completion.js.map +1 -0
  144. package/dist/core/costs.d.ts +90 -0
  145. package/dist/core/costs.d.ts.map +1 -0
  146. package/dist/core/costs.js +147 -0
  147. package/dist/core/costs.js.map +1 -0
  148. package/dist/core/decoder.d.ts +7 -0
  149. package/dist/core/decoder.d.ts.map +1 -0
  150. package/dist/core/decoder.js +38 -0
  151. package/dist/core/decoder.js.map +1 -0
  152. package/dist/core/discovery.d.ts +38 -0
  153. package/dist/core/discovery.d.ts.map +1 -0
  154. package/dist/core/discovery.js +208 -0
  155. package/dist/core/discovery.js.map +1 -0
  156. package/dist/core/extension.d.ts +83 -0
  157. package/dist/core/extension.d.ts.map +1 -0
  158. package/dist/core/extension.js +429 -0
  159. package/dist/core/extension.js.map +1 -0
  160. package/dist/core/inspect.d.ts +62 -0
  161. package/dist/core/inspect.d.ts.map +1 -0
  162. package/dist/core/inspect.js +303 -0
  163. package/dist/core/inspect.js.map +1 -0
  164. package/dist/core/introspection.d.ts +8 -0
  165. package/dist/core/introspection.d.ts.map +1 -0
  166. package/dist/core/introspection.js +8 -0
  167. package/dist/core/introspection.js.map +1 -0
  168. package/dist/core/monitor.d.ts +42 -0
  169. package/dist/core/monitor.d.ts.map +1 -0
  170. package/dist/core/monitor.js +188 -0
  171. package/dist/core/monitor.js.map +1 -0
  172. package/dist/core/rent_projection.d.ts +153 -0
  173. package/dist/core/rent_projection.d.ts.map +1 -0
  174. package/dist/core/rent_projection.js +146 -0
  175. package/dist/core/rent_projection.js.map +1 -0
  176. package/dist/core/scvalTranslator.d.ts +11 -0
  177. package/dist/core/scvalTranslator.d.ts.map +1 -0
  178. package/dist/core/scvalTranslator.js +36 -0
  179. package/dist/core/scvalTranslator.js.map +1 -0
  180. package/dist/core/state_diff.d.ts +45 -0
  181. package/dist/core/state_diff.d.ts.map +1 -0
  182. package/dist/core/state_diff.js +112 -0
  183. package/dist/core/state_diff.js.map +1 -0
  184. package/dist/core/status.d.ts +24 -0
  185. package/dist/core/status.d.ts.map +1 -0
  186. package/dist/core/status.js +57 -0
  187. package/dist/core/status.js.map +1 -0
  188. package/dist/core/vault.d.ts +37 -0
  189. package/dist/core/vault.d.ts.map +1 -0
  190. package/dist/core/vault.js +179 -0
  191. package/dist/core/vault.js.map +1 -0
  192. package/dist/core/watch.d.ts +36 -0
  193. package/dist/core/watch.d.ts.map +1 -0
  194. package/dist/core/watch.js +176 -0
  195. package/dist/core/watch.js.map +1 -0
  196. package/dist/daemon/loop.d.ts +38 -0
  197. package/dist/daemon/loop.d.ts.map +1 -0
  198. package/dist/daemon/loop.js +169 -0
  199. package/dist/daemon/loop.js.map +1 -0
  200. package/dist/db/backup.d.ts +12 -0
  201. package/dist/db/backup.d.ts.map +1 -0
  202. package/dist/db/backup.js +132 -0
  203. package/dist/db/backup.js.map +1 -0
  204. package/dist/db/budget.d.ts +17 -0
  205. package/dist/db/budget.d.ts.map +1 -0
  206. package/dist/db/budget.js +31 -0
  207. package/dist/db/budget.js.map +1 -0
  208. package/dist/db/database.d.ts +6 -0
  209. package/dist/db/database.d.ts.map +1 -0
  210. package/dist/db/database.js +134 -0
  211. package/dist/db/database.js.map +1 -0
  212. package/dist/db/migrations/.gitkeep +1 -0
  213. package/dist/db/migrations/001_resource_usage_logs.sql +28 -0
  214. package/dist/db/migrator.d.ts +28 -0
  215. package/dist/db/migrator.d.ts.map +1 -0
  216. package/dist/db/migrator.js +75 -0
  217. package/dist/db/migrator.js.map +1 -0
  218. package/dist/db/repositories.d.ts +474 -0
  219. package/dist/db/repositories.d.ts.map +1 -0
  220. package/dist/db/repositories.js +809 -0
  221. package/dist/db/repositories.js.map +1 -0
  222. package/dist/db/schema.sql +207 -0
  223. package/dist/index.d.ts +3 -0
  224. package/dist/index.d.ts.map +1 -0
  225. package/dist/index.js +47 -0
  226. package/dist/index.js.map +1 -0
  227. package/dist/lib.d.ts +20 -0
  228. package/dist/lib.d.ts.map +1 -0
  229. package/dist/lib.js +16 -0
  230. package/dist/lib.js.map +1 -0
  231. package/dist/logging/index.d.ts +22 -0
  232. package/dist/logging/index.d.ts.map +1 -0
  233. package/dist/logging/index.js +34 -0
  234. package/dist/logging/index.js.map +1 -0
  235. package/dist/logging/logger.d.ts +14 -0
  236. package/dist/logging/logger.d.ts.map +1 -0
  237. package/dist/logging/logger.js +135 -0
  238. package/dist/logging/logger.js.map +1 -0
  239. package/dist/logging/types.d.ts +10 -0
  240. package/dist/logging/types.d.ts.map +1 -0
  241. package/dist/logging/types.js +2 -0
  242. package/dist/logging/types.js.map +1 -0
  243. package/dist/mcp/index.d.ts +3 -0
  244. package/dist/mcp/index.d.ts.map +1 -0
  245. package/dist/mcp/index.js +14 -0
  246. package/dist/mcp/index.js.map +1 -0
  247. package/dist/mcp/server.d.ts +10 -0
  248. package/dist/mcp/server.d.ts.map +1 -0
  249. package/dist/mcp/server.js +51 -0
  250. package/dist/mcp/server.js.map +1 -0
  251. package/dist/mcp/tools/get-extension-costs.d.ts +4 -0
  252. package/dist/mcp/tools/get-extension-costs.d.ts.map +1 -0
  253. package/dist/mcp/tools/get-extension-costs.js +33 -0
  254. package/dist/mcp/tools/get-extension-costs.js.map +1 -0
  255. package/dist/mcp/tools/get_contract_status.d.ts +8 -0
  256. package/dist/mcp/tools/get_contract_status.d.ts.map +1 -0
  257. package/dist/mcp/tools/get_contract_status.js +33 -0
  258. package/dist/mcp/tools/get_contract_status.js.map +1 -0
  259. package/dist/rpc/client.d.ts +154 -0
  260. package/dist/rpc/client.d.ts.map +1 -0
  261. package/dist/rpc/client.js +766 -0
  262. package/dist/rpc/client.js.map +1 -0
  263. package/dist/utils/config.d.ts +51 -0
  264. package/dist/utils/config.d.ts.map +1 -0
  265. package/dist/utils/config.js +86 -0
  266. package/dist/utils/config.js.map +1 -0
  267. package/dist/utils/formatting.d.ts +10 -0
  268. package/dist/utils/formatting.d.ts.map +1 -0
  269. package/dist/utils/formatting.js +66 -0
  270. package/dist/utils/formatting.js.map +1 -0
  271. package/dist/utils/watch-config.d.ts +13 -0
  272. package/dist/utils/watch-config.d.ts.map +1 -0
  273. package/dist/utils/watch-config.js +32 -0
  274. package/dist/utils/watch-config.js.map +1 -0
  275. package/package.json +73 -0
@@ -0,0 +1,37 @@
1
+ export interface VaultConfig {
2
+ url: string;
3
+ token: string;
4
+ namespace?: string;
5
+ }
6
+ export declare class VaultAuthError extends Error {
7
+ constructor(message?: string);
8
+ }
9
+ export declare class VaultSecretNotFoundError extends Error {
10
+ constructor(secretPath: string);
11
+ }
12
+ export declare class VaultSecretError extends Error {
13
+ constructor(message: string);
14
+ }
15
+ /**
16
+ * VaultResolver - Retrieve Stellar secret keys dynamically from HashiCorp Vault
17
+ *
18
+ * Supports KV v1 and KV v2 secret engines.
19
+ *
20
+ * Secret lookup order for field names:
21
+ * secret_key, private_key, secret, value, stellar_secret, key
22
+ *
23
+ * You can also specify a custom field with a fragment: vault:secret/data/foo#my_field
24
+ */
25
+ export declare class VaultResolver {
26
+ private url;
27
+ private token;
28
+ private namespace?;
29
+ constructor(config: VaultConfig);
30
+ /**
31
+ * Fetch a Stellar secret key from Vault
32
+ * @param secretPath Vault secret path, optionally with #field suffix
33
+ * @returns Stellar secret key string (starts with S)
34
+ */
35
+ getSecret(secretPath: string): Promise<string>;
36
+ }
37
+ //# sourceMappingURL=vault.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vault.d.ts","sourceRoot":"","sources":["../../src/core/vault.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,WAAW;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,cAAe,SAAQ,KAAK;gBACzB,OAAO,GAAE,MAAsC;CAI9D;AAED,qBAAa,wBAAyB,SAAQ,KAAK;gBACnC,UAAU,EAAE,MAAM;CAIjC;AAED,qBAAa,gBAAiB,SAAQ,KAAK;gBAC3B,OAAO,EAAE,MAAM;CAI9B;AAED;;;;;;;;;GASG;AACH,qBAAa,aAAa;IACtB,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,SAAS,CAAC,CAAS;gBAEf,MAAM,EAAE,WAAW;IAa/B;;;;OAIG;IACG,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAmIvD"}
@@ -0,0 +1,179 @@
1
+ /* eslint-disable @typescript-eslint/no-unused-vars */
2
+ import { getLogger } from "../logging/index.js";
3
+ const logger = getLogger().child({ component: "VaultResolver" });
4
+ export class VaultAuthError extends Error {
5
+ constructor(message = "Vault authentication failed") {
6
+ super(message);
7
+ this.name = "VaultAuthError";
8
+ }
9
+ }
10
+ export class VaultSecretNotFoundError extends Error {
11
+ constructor(secretPath) {
12
+ super(`Secret not found in Vault at path: ${secretPath}`);
13
+ this.name = "VaultSecretNotFoundError";
14
+ }
15
+ }
16
+ export class VaultSecretError extends Error {
17
+ constructor(message) {
18
+ super(message);
19
+ this.name = "VaultSecretError";
20
+ }
21
+ }
22
+ /**
23
+ * VaultResolver - Retrieve Stellar secret keys dynamically from HashiCorp Vault
24
+ *
25
+ * Supports KV v1 and KV v2 secret engines.
26
+ *
27
+ * Secret lookup order for field names:
28
+ * secret_key, private_key, secret, value, stellar_secret, key
29
+ *
30
+ * You can also specify a custom field with a fragment: vault:secret/data/foo#my_field
31
+ */
32
+ export class VaultResolver {
33
+ url;
34
+ token;
35
+ namespace;
36
+ constructor(config) {
37
+ if (!config.url) {
38
+ throw new VaultSecretError("Vault URL is required");
39
+ }
40
+ if (!config.token) {
41
+ throw new VaultAuthError("Vault token is required");
42
+ }
43
+ // Normalize URL (strip trailing slash)
44
+ this.url = config.url.replace(/\/$/, "");
45
+ this.token = config.token;
46
+ this.namespace = config.namespace;
47
+ }
48
+ /**
49
+ * Fetch a Stellar secret key from Vault
50
+ * @param secretPath Vault secret path, optionally with #field suffix
51
+ * @returns Stellar secret key string (starts with S)
52
+ */
53
+ async getSecret(secretPath) {
54
+ // Parse field fragment
55
+ let path = secretPath;
56
+ let fieldName = null;
57
+ const hashIdx = secretPath.indexOf("#");
58
+ if (hashIdx !== -1) {
59
+ path = secretPath.slice(0, hashIdx);
60
+ fieldName = secretPath.slice(hashIdx + 1);
61
+ }
62
+ // Strip leading slash
63
+ if (path.startsWith("/")) {
64
+ path = path.slice(1);
65
+ }
66
+ const url = `${this.url}/v1/${path}`;
67
+ const headers = {
68
+ "X-Vault-Token": this.token,
69
+ "Accept": "application/json",
70
+ };
71
+ if (this.namespace) {
72
+ headers["X-Vault-Namespace"] = this.namespace;
73
+ }
74
+ let res;
75
+ try {
76
+ res = await fetch(url, { headers });
77
+ }
78
+ catch (err) {
79
+ const message = err instanceof Error ? err.message : String(err);
80
+ logger.error(`Vault request failed: ${message}`);
81
+ throw new VaultSecretError(`Vault request failed: ${message}`);
82
+ }
83
+ if (!res.ok) {
84
+ if (res.status === 403 || res.status === 401) {
85
+ throw new VaultAuthError(`Vault authentication failed (${res.status})`);
86
+ }
87
+ if (res.status === 404) {
88
+ throw new VaultSecretNotFoundError(path);
89
+ }
90
+ let body = "";
91
+ try {
92
+ body = await res.text();
93
+ }
94
+ catch { /* ignore */ }
95
+ throw new VaultSecretError(`Vault request failed with status ${res.status}: ${body}`);
96
+ }
97
+ let json;
98
+ try {
99
+ json = await res.json();
100
+ }
101
+ catch (err) {
102
+ throw new VaultSecretError("Failed to parse Vault response as JSON");
103
+ }
104
+ // KV v2: data.data.{field}
105
+ // KV v1: data.{field}
106
+ let secretData;
107
+ if (json?.data?.data && typeof json.data.data === "object") {
108
+ secretData = json.data.data;
109
+ }
110
+ else if (json?.data && typeof json.data === "object") {
111
+ secretData = json.data;
112
+ }
113
+ if (!secretData) {
114
+ throw new VaultSecretError(`No secret data found at Vault path: ${path}`);
115
+ }
116
+ let secretValue;
117
+ if (fieldName) {
118
+ secretValue = secretData[fieldName];
119
+ if (secretValue === undefined) {
120
+ throw new VaultSecretError(`Field '${fieldName}' not found in Vault secret at path: ${path}`);
121
+ }
122
+ }
123
+ else {
124
+ // Try common field names in order
125
+ const candidates = [
126
+ "secret_key",
127
+ "private_key",
128
+ "secret",
129
+ "value",
130
+ "stellar_secret",
131
+ "key",
132
+ ];
133
+ for (const key of candidates) {
134
+ if (typeof secretData[key] === "string") {
135
+ secretValue = secretData[key];
136
+ break;
137
+ }
138
+ }
139
+ // Fallback: if there's exactly one string field that looks like a Stellar secret, use it
140
+ if (!secretValue) {
141
+ const stringEntries = Object.entries(secretData).filter(([, v]) => typeof v === "string");
142
+ if (stringEntries.length === 1) {
143
+ const first = stringEntries[0];
144
+ if (first)
145
+ secretValue = first[1];
146
+ }
147
+ else {
148
+ // Try to find any Stellar-looking secret
149
+ const stellarEntry = stringEntries.find(([, v]) => isStellarSecret(v));
150
+ if (stellarEntry) {
151
+ secretValue = stellarEntry[1];
152
+ }
153
+ }
154
+ }
155
+ }
156
+ if (!secretValue || typeof secretValue !== "string") {
157
+ throw new VaultSecretError(`No valid secret key found in Vault response at path: ${path}. ` +
158
+ `Expected one of: secret_key, private_key, secret, value, stellar_secret, key`);
159
+ }
160
+ if (!isStellarSecret(secretValue)) {
161
+ throw new VaultSecretError(`Secret retrieved from Vault is not a valid Stellar secret key format. ` +
162
+ `Expected 56-character string starting with 'S', got: ${maskSecret(secretValue)}`);
163
+ }
164
+ logger.debug(`Successfully resolved secret from Vault path: ${path}`);
165
+ return secretValue;
166
+ }
167
+ }
168
+ /**
169
+ * Check if a string looks like a Stellar secret key
170
+ */
171
+ function isStellarSecret(value) {
172
+ return typeof value === "string" && value.startsWith("S") && value.length === 56;
173
+ }
174
+ function maskSecret(secret) {
175
+ if (secret.length <= 8)
176
+ return "***";
177
+ return `${secret.slice(0, 4)}...${secret.slice(-4)}`;
178
+ }
179
+ //# sourceMappingURL=vault.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vault.js","sourceRoot":"","sources":["../../src/core/vault.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ import type Database from "better-sqlite3";
2
+ import { ContractInstanceResult, SorokeepLedgerEntryResult } from "../rpc/client.js";
3
+ export interface WatchOptions {
4
+ contractId: string;
5
+ network: string;
6
+ name?: string;
7
+ rpcUrl?: string;
8
+ storageKeys?: string[];
9
+ pollIntervalSeconds?: number;
10
+ /**
11
+ * When true, skip the introspection cache and always fetch fresh data from
12
+ * the RPC. Useful for manual `watch` commands where the user expects an
13
+ * immediate refresh. Defaults to false.
14
+ */
15
+ forceRefresh?: boolean;
16
+ noIntrospection?: boolean;
17
+ }
18
+ export type WatchResult = {
19
+ success: true;
20
+ contractId: string;
21
+ instance: ContractInstanceResult;
22
+ wasm: SorokeepLedgerEntryResult | null;
23
+ wasmWarning?: string;
24
+ /** True when the result was served from the introspection cache (no RPC call). */
25
+ fromCache?: boolean;
26
+ } | {
27
+ success: false;
28
+ contractId: string;
29
+ error: string;
30
+ };
31
+ /**
32
+ * Core logic to discover and register a contract and its key entries.
33
+ * - This is `Layer 1` of the architecture.
34
+ */
35
+ export declare function watchContract(db: Database.Database, options: WatchOptions): Promise<WatchResult>;
36
+ //# sourceMappingURL=watch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watch.d.ts","sourceRoot":"","sources":["../../src/core/watch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAoB,sBAAsB,EAAE,yBAAyB,EAAE,MAAM,kBAAkB,CAAC;AAavG,MAAM,WAAW,YAAY;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;OAIG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,MAAM,WAAW,GACjB;IACF,OAAO,EAAE,IAAI,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,sBAAsB,CAAC;IACjC,IAAI,EAAE,yBAAyB,GAAG,IAAI,CAAC;IACvC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kFAAkF;IAClF,SAAS,CAAC,EAAE,OAAO,CAAC;CACvB,GAAG;IACA,OAAO,EAAE,KAAK,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;;GAGG;AACH,wBAAsB,aAAa,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CAuLtG"}
@@ -0,0 +1,176 @@
1
+ import { StellarRpcClient } from "../rpc/client.js";
2
+ import { insertContract, upsertEntry, updateLastCheckedLedger, getContract, updateLastIntrospectedAt, isIntrospectionCacheValid, } from "../db/repositories.js";
3
+ import { getLogger } from "../logging/index.js";
4
+ const logger = getLogger();
5
+ /**
6
+ * Core logic to discover and register a contract and its key entries.
7
+ * - This is `Layer 1` of the architecture.
8
+ */
9
+ export async function watchContract(db, options) {
10
+ logger.debug(`Watching contract ${options.contractId} on ${options.network}`);
11
+ const { contractId, network, name, rpcUrl, storageKeys, forceRefresh = false, pollIntervalSeconds } = options;
12
+ // Basic Validation
13
+ if (!contractId.startsWith("C") || contractId.length !== 56) {
14
+ logger.error(`Invalid Contract ID format: ${contractId}`);
15
+ return {
16
+ success: false,
17
+ contractId,
18
+ error: "Invalid Contract ID format. Must be a 56-character string starting with 'C'.",
19
+ };
20
+ }
21
+ const client = new StellarRpcClient(network, rpcUrl);
22
+ try {
23
+ // 0. Check if already registered on a different network
24
+ const existing = getContract(db, contractId);
25
+ if (existing && existing.network !== network) {
26
+ logger.warn(`Contract ${contractId} is already registered on ${existing.network}. To watch on ${network}, unwatch it first.`);
27
+ return {
28
+ success: false,
29
+ contractId,
30
+ error: `Contract ${contractId} is already registered on ${existing.network}. To watch on ${network}, unwatch it first.`,
31
+ };
32
+ }
33
+ // ── Introspection cache check ─────────────────────────────────────────
34
+ // If the contract has been introspected recently (within 24 h) and the
35
+ // caller did not request a forced refresh, skip the RPC calls entirely
36
+ // and return the cached result.
37
+ if (!forceRefresh && isIntrospectionCacheValid(db, contractId)) {
38
+ logger.debug(`Contract ${contractId}: introspection cache is valid — skipping RPC calls`);
39
+ // Build a minimal success result from cached DB data.
40
+ // We return success=true without real instance/wasm objects so that
41
+ // callers (CLI, daemon) know the operation succeeded. The actual
42
+ // entry data remains accurate in the DB from the last introspection.
43
+ return {
44
+ success: true,
45
+ contractId,
46
+ // Provide stub values — callers that need live RPC data should
47
+ // use forceRefresh=true.
48
+ instance: {
49
+ entryKeyXdr: "",
50
+ latestLedger: 0,
51
+ liveUntilLedgerSeq: 0,
52
+ lastModifiedLedgerSeq: 0,
53
+ remainingTTL: 0,
54
+ executableType: "cached",
55
+ wasmHash: existing?.wasm_hash ?? null,
56
+ },
57
+ wasm: null,
58
+ fromCache: true,
59
+ };
60
+ }
61
+ // 1. Fetch Contract Instance Entry
62
+ const instanceEntry = await client.getContractInstanceEntry(contractId);
63
+ if (!instanceEntry) {
64
+ return {
65
+ success: false,
66
+ contractId,
67
+ error: `Contract ${contractId} not found on ${network}.`,
68
+ };
69
+ }
70
+ // 2. Fetch WASM code entry if applicable
71
+ let wasmEntry = null;
72
+ let wasmWarning;
73
+ if (instanceEntry.wasmHash && !options.noIntrospection) {
74
+ wasmEntry = await client.getWasmCodeEntry(instanceEntry.wasmHash);
75
+ if (!wasmEntry) {
76
+ wasmWarning = `WASM entry for hash ${instanceEntry.wasmHash} not found. It might be archived.`;
77
+ }
78
+ }
79
+ // 3. Fetch Manual Storage Keys if provided
80
+ const extraEntries = [];
81
+ if (storageKeys && storageKeys.length > 0) {
82
+ const ttls = await client.getEntryTTLs(storageKeys);
83
+ extraEntries.push(...ttls.entries);
84
+ }
85
+ // 3.5. Introspection: Fetch monitored keys if contract supports it
86
+ const introspectedEntries = [];
87
+ if (!options.noIntrospection) {
88
+ try {
89
+ const keys = await client.getMonitoredKeys(contractId);
90
+ if (keys.length > 0) {
91
+ const ttls = await client.getEntryTTLs(keys);
92
+ introspectedEntries.push(...ttls.entries);
93
+ }
94
+ }
95
+ catch (error) {
96
+ logger.debug(`Introspection skipped for ${contractId}: ${error}`);
97
+ }
98
+ }
99
+ // 4. Store in Database
100
+ // Note: insertContract uses ON CONFLICT(id) DO UPDATE
101
+ insertContract(db, {
102
+ id: contractId,
103
+ name: name,
104
+ network: network,
105
+ wasm_hash: instanceEntry.wasmHash ?? undefined,
106
+ poll_interval_seconds: pollIntervalSeconds ?? null,
107
+ });
108
+ // Store Instance Entry
109
+ upsertEntry(db, {
110
+ contract_id: contractId,
111
+ entry_key_xdr: instanceEntry.entryKeyXdr,
112
+ entry_type: "instance",
113
+ label: "Contract Instance",
114
+ live_until_ledger: instanceEntry.liveUntilLedgerSeq,
115
+ last_modified_ledger: instanceEntry.lastModifiedLedgerSeq,
116
+ discovery_source: "deterministic",
117
+ });
118
+ // Store WASM Entry if found
119
+ if (wasmEntry) {
120
+ upsertEntry(db, {
121
+ contract_id: contractId,
122
+ entry_key_xdr: wasmEntry.entryKeyXdr,
123
+ entry_type: "wasm",
124
+ label: "WASM Code",
125
+ live_until_ledger: wasmEntry.liveUntilLedgerSeq,
126
+ last_modified_ledger: wasmEntry.lastModifiedLedgerSeq,
127
+ discovery_source: "deterministic",
128
+ });
129
+ }
130
+ // Store Manual Storage Entries
131
+ for (const entry of extraEntries) {
132
+ upsertEntry(db, {
133
+ contract_id: contractId,
134
+ entry_key_xdr: entry.entryKeyXdr,
135
+ entry_type: "persistent", // Defaulting to persistent for manual keys
136
+ label: "Manual Storage Entry",
137
+ live_until_ledger: entry.liveUntilLedgerSeq,
138
+ last_modified_ledger: entry.lastModifiedLedgerSeq,
139
+ discovery_source: "manual",
140
+ });
141
+ }
142
+ // Store Introspected Entries
143
+ for (const entry of introspectedEntries) {
144
+ upsertEntry(db, {
145
+ contract_id: contractId,
146
+ entry_key_xdr: entry.entryKeyXdr,
147
+ entry_type: "persistent", // Defaulting to persistent for introspected keys
148
+ label: "Introspected Storage Entry",
149
+ live_until_ledger: entry.liveUntilLedgerSeq,
150
+ last_modified_ledger: entry.lastModifiedLedgerSeq,
151
+ discovery_source: "introspection",
152
+ });
153
+ }
154
+ // Update last checked ledger
155
+ updateLastCheckedLedger(db, contractId, instanceEntry.latestLedger);
156
+ // ── Record successful introspection in the cache ───────────────────────
157
+ updateLastIntrospectedAt(db, contractId, new Date().toISOString());
158
+ return {
159
+ success: true,
160
+ contractId,
161
+ instance: instanceEntry,
162
+ wasm: wasmEntry,
163
+ wasmWarning,
164
+ fromCache: false,
165
+ };
166
+ }
167
+ catch (error) {
168
+ logger.error(`Error watching contract ${contractId}: ${error.message}`, error);
169
+ return {
170
+ success: false,
171
+ contractId,
172
+ error: error.message,
173
+ };
174
+ }
175
+ }
176
+ //# sourceMappingURL=watch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watch.js","sourceRoot":"","sources":["../../src/core/watch.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ import type Database from "better-sqlite3";
2
+ import { type MonitorCycleResult } from "../core/monitor.js";
3
+ export interface DaemonOptions {
4
+ /** Polling interval in milliseconds. Defaults to 300000 (5 minutes). */
5
+ intervalMs?: number;
6
+ /** Optional RPC endpoint URL override. */
7
+ rpcUrl?: string;
8
+ /** Optional sponsor secret key for auto-extensions */
9
+ feeSponsorSecret?: string;
10
+ /** How frequently to run vacuum maintenance. Defaults to 24 hours. */
11
+ vacuumIntervalMs?: number;
12
+ /** Called after every cycle with the result (or null + error on failure). */
13
+ onCycle?: (result: MonitorCycleResult | null, error?: Error) => void;
14
+ }
15
+ /**
16
+ * Start the monitoring daemon.
17
+ *
18
+ * Runs one cycle immediately, then schedules repeating cycles at the
19
+ * configured interval. Never rejects — errors from individual cycles
20
+ * are caught, logged, and forwarded to the optional `onCycle` callback.
21
+ *
22
+ * Calling `startDaemon` while a daemon is already running will stop the
23
+ * previous loop first (kills the old timer), then start fresh.
24
+ *
25
+ * Re-entrance guard: if a cycle is still in-flight when the next interval
26
+ * fires, that tick is skipped silently.
27
+ */
28
+ export declare function startDaemon(db: Database.Database, network: string, options?: DaemonOptions): Promise<void>;
29
+ /**
30
+ * Stop the monitoring daemon.
31
+ *
32
+ * Clears the interval timer so no further cycles are scheduled.
33
+ * Idempotent — safe to call multiple times or before `startDaemon`.
34
+ * Does NOT abort a cycle that is currently in-flight; it will finish
35
+ * naturally, but no new cycle will be scheduled after it.
36
+ */
37
+ export declare function stopDaemon(): void;
38
+ //# sourceMappingURL=loop.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loop.d.ts","sourceRoot":"","sources":["../../src/daemon/loop.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAmB,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAiB9E,MAAM,WAAW,aAAa;IAC1B,wEAAwE;IACxE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0CAA0C;IAC1C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sDAAsD;IACtD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,sEAAsE;IACtE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,6EAA6E;IAC7E,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,GAAG,IAAI,EAAE,KAAK,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;CACxE;AAcD;;;;;;;;;;;;GAYG;AACH,wBAAsB,WAAW,CAC7B,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,aAAa,GACxB,OAAO,CAAC,IAAI,CAAC,CAoBf;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,IAAI,IAAI,CAUjC"}
@@ -0,0 +1,169 @@
1
+ import { runMonitorCycle } from "../core/monitor.js";
2
+ import { deliverPendingAlerts } from "../alerts/dispatcher.js";
3
+ import { vacuumDatabase } from "../db/database.js";
4
+ import { aggregateDailyCostSnapshots, getAllContracts } from "../db/repositories.js";
5
+ import { getLogger } from "../logging/index.js";
6
+ // Resolve the child logger lazily so that a runtime reconfiguration of the
7
+ // global logger (e.g. the daemon command's `--log-format json`) is in effect
8
+ // by the time the loop emits its first line.
9
+ let loopLogger = null;
10
+ function logger() {
11
+ return (loopLogger ??= getLogger().child({ component: "DaemonLoop" }));
12
+ }
13
+ // ─── Module-level state ───────────────────────────────────────────────────────
14
+ const DEFAULT_INTERVAL_MS = 300_000; // 5 minutes
15
+ const DEFAULT_VACUUM_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24 hours
16
+ let intervalHandle = null;
17
+ let cycleInFlight = false;
18
+ let vacuumIntervalMs = DEFAULT_VACUUM_INTERVAL_MS;
19
+ let lastVacuumAt = 0;
20
+ // ─── Core API ─────────────────────────────────────────────────────────────────
21
+ /**
22
+ * Start the monitoring daemon.
23
+ *
24
+ * Runs one cycle immediately, then schedules repeating cycles at the
25
+ * configured interval. Never rejects — errors from individual cycles
26
+ * are caught, logged, and forwarded to the optional `onCycle` callback.
27
+ *
28
+ * Calling `startDaemon` while a daemon is already running will stop the
29
+ * previous loop first (kills the old timer), then start fresh.
30
+ *
31
+ * Re-entrance guard: if a cycle is still in-flight when the next interval
32
+ * fires, that tick is skipped silently.
33
+ */
34
+ export async function startDaemon(db, network, options) {
35
+ // Kill any existing loop before starting a new one.
36
+ stopDaemon();
37
+ const intervalMs = options?.intervalMs ?? DEFAULT_INTERVAL_MS;
38
+ vacuumIntervalMs = options?.vacuumIntervalMs ?? DEFAULT_VACUUM_INTERVAL_MS;
39
+ const rpcUrl = options?.rpcUrl;
40
+ const onCycle = options?.onCycle;
41
+ const effectiveIntervalMs = resolvePollIntervalMs(db, network, intervalMs);
42
+ lastVacuumAt = Date.now();
43
+ logger().info(`Daemon starting — network: ${network}, interval: ${intervalMs}ms`);
44
+ // Run the initial cycle immediately.
45
+ await executeCycle(db, network, rpcUrl, options?.feeSponsorSecret, onCycle);
46
+ // Schedule repeating cycles.
47
+ intervalHandle = setInterval(() => {
48
+ void scheduledTick(db, network, rpcUrl, options?.feeSponsorSecret, onCycle);
49
+ }, effectiveIntervalMs);
50
+ }
51
+ /**
52
+ * Stop the monitoring daemon.
53
+ *
54
+ * Clears the interval timer so no further cycles are scheduled.
55
+ * Idempotent — safe to call multiple times or before `startDaemon`.
56
+ * Does NOT abort a cycle that is currently in-flight; it will finish
57
+ * naturally, but no new cycle will be scheduled after it.
58
+ */
59
+ export function stopDaemon() {
60
+ if (intervalHandle !== null) {
61
+ clearInterval(intervalHandle);
62
+ intervalHandle = null;
63
+ logger().info("Daemon stopped");
64
+ }
65
+ // Do NOT reset cycleInFlight here — let executeCycle's finally-block
66
+ // handle it when the in-flight cycle completes. Resetting it early
67
+ // breaks the re-entrance guard if startDaemon() is called before the
68
+ // old cycle finishes.
69
+ }
70
+ // ─── Private ──────────────────────────────────────────────────────────────────
71
+ /**
72
+ * Execute a single monitoring cycle with full error isolation.
73
+ *
74
+ * Sets the `cycleInFlight` flag to prevent re-entrance, runs the cycle,
75
+ * invokes the `onCycle` callback, and guarantees that no thrown error
76
+ * (from the cycle or the callback) can kill the daemon.
77
+ */
78
+ async function executeCycle(db, network, rpcUrl, feeSponsorSecret, onCycle) {
79
+ cycleInFlight = true;
80
+ try {
81
+ const result = await runMonitorCycle(db, network, rpcUrl, feeSponsorSecret);
82
+ logger().debug(`Cycle complete — checked: ${result.contractsChecked}, ` +
83
+ `updated: ${result.entriesUpdated}, ` +
84
+ `crossed: ${result.thresholdsCrossed}, ` +
85
+ `resolved: ${result.alertsResolved}, ` +
86
+ `extended: ${result.extensionsTriggered}, ` +
87
+ `errors: ${result.errors.length}`);
88
+ // Step 2: deliver any pending alerts that accumulated during detection.
89
+ // Errors here are isolated — they must NOT kill the cycle or surface to onCycle.
90
+ try {
91
+ const delivery = await deliverPendingAlerts(db, network);
92
+ if (delivery.attempted > 0) {
93
+ logger().info(`Delivery — attempted: ${delivery.attempted}, ` +
94
+ `delivered: ${delivery.delivered}, failed: ${delivery.failed}`);
95
+ }
96
+ }
97
+ catch (deliveryErr) {
98
+ // This should never happen (deliverPendingAlerts never throws),
99
+ // but guard defensively.
100
+ logger().error("deliverPendingAlerts threw unexpectedly", deliveryErr);
101
+ }
102
+ // Step 3: aggregate daily cost snapshots for past extension history.
103
+ try {
104
+ aggregateDailyCostSnapshots(db);
105
+ }
106
+ catch (snapshotErr) {
107
+ logger().error("aggregateDailyCostSnapshots threw unexpectedly", snapshotErr);
108
+ }
109
+ safeOnCycle(onCycle, result, undefined);
110
+ }
111
+ catch (err) {
112
+ const error = err instanceof Error ? err : new Error(String(err));
113
+ logger().error(`Cycle failed: ${error.message}`, err);
114
+ safeOnCycle(onCycle, null, error);
115
+ }
116
+ finally {
117
+ cycleInFlight = false;
118
+ }
119
+ }
120
+ async function scheduledTick(db, network, rpcUrl, feeSponsorSecret, onCycle) {
121
+ if (cycleInFlight) {
122
+ logger().debug("Skipping tick — previous cycle still in flight");
123
+ return;
124
+ }
125
+ await runScheduledVacuum(db);
126
+ await executeCycle(db, network, rpcUrl, feeSponsorSecret, onCycle);
127
+ }
128
+ async function runScheduledVacuum(db) {
129
+ if (Date.now() - lastVacuumAt < vacuumIntervalMs) {
130
+ return;
131
+ }
132
+ if (db.inTransaction) {
133
+ logger().info("Skipping scheduled vacuum — database has an active transaction");
134
+ return;
135
+ }
136
+ logger().info("Scheduled maintenance: starting database vacuum");
137
+ const vacuumed = vacuumDatabase(db);
138
+ if (vacuumed) {
139
+ lastVacuumAt = Date.now();
140
+ logger().info("Scheduled maintenance: database vacuum completed");
141
+ }
142
+ else {
143
+ logger().info("Scheduled maintenance: database vacuum skipped due to busy database");
144
+ }
145
+ }
146
+ /**
147
+ * Invoke the onCycle callback without letting it kill the daemon.
148
+ */
149
+ function safeOnCycle(onCycle, result, error) {
150
+ if (!onCycle)
151
+ return;
152
+ try {
153
+ onCycle(result, error);
154
+ }
155
+ catch (cbErr) {
156
+ logger().error("onCycle callback threw — ignoring", cbErr);
157
+ }
158
+ }
159
+ function resolvePollIntervalMs(db, network, fallbackIntervalMs) {
160
+ const contracts = getAllContracts(db).filter((contract) => contract.network === network);
161
+ const overrides = contracts
162
+ .map((contract) => contract.poll_interval_seconds)
163
+ .filter((value) => typeof value === "number" && value > 0);
164
+ if (overrides.length === 0) {
165
+ return fallbackIntervalMs;
166
+ }
167
+ return Math.min(...overrides) * 1000;
168
+ }
169
+ //# sourceMappingURL=loop.js.map