palaryn 0.1.0 → 0.3.2

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 (344) hide show
  1. package/README.md +243 -588
  2. package/dist/sdk/typescript/src/client.js +2 -2
  3. package/dist/sdk/typescript/src/client.js.map +1 -1
  4. package/dist/src/anomaly/detector.d.ts +7 -4
  5. package/dist/src/anomaly/detector.d.ts.map +1 -1
  6. package/dist/src/anomaly/detector.js +22 -12
  7. package/dist/src/anomaly/detector.js.map +1 -1
  8. package/dist/src/audit/logger.d.ts +10 -0
  9. package/dist/src/audit/logger.d.ts.map +1 -1
  10. package/dist/src/audit/logger.js +52 -38
  11. package/dist/src/audit/logger.js.map +1 -1
  12. package/dist/src/auth/routes.d.ts.map +1 -1
  13. package/dist/src/auth/routes.js +35 -0
  14. package/dist/src/auth/routes.js.map +1 -1
  15. package/dist/src/budget/manager.d.ts +5 -0
  16. package/dist/src/budget/manager.d.ts.map +1 -1
  17. package/dist/src/budget/manager.js +32 -0
  18. package/dist/src/budget/manager.js.map +1 -1
  19. package/dist/src/budget/model-pricing.d.ts +20 -0
  20. package/dist/src/budget/model-pricing.d.ts.map +1 -0
  21. package/dist/src/budget/model-pricing.js +107 -0
  22. package/dist/src/budget/model-pricing.js.map +1 -0
  23. package/dist/src/budget/usage-extractor.d.ts +3 -1
  24. package/dist/src/budget/usage-extractor.d.ts.map +1 -1
  25. package/dist/src/budget/usage-extractor.js +47 -3
  26. package/dist/src/budget/usage-extractor.js.map +1 -1
  27. package/dist/src/config/defaults.d.ts.map +1 -1
  28. package/dist/src/config/defaults.js +65 -13
  29. package/dist/src/config/defaults.js.map +1 -1
  30. package/dist/src/dlp/tool-patterns.d.ts +7 -0
  31. package/dist/src/dlp/tool-patterns.d.ts.map +1 -0
  32. package/dist/src/dlp/tool-patterns.js +34 -0
  33. package/dist/src/dlp/tool-patterns.js.map +1 -0
  34. package/dist/src/executor/filesystem-executor.d.ts +28 -0
  35. package/dist/src/executor/filesystem-executor.d.ts.map +1 -0
  36. package/dist/src/executor/filesystem-executor.js +192 -0
  37. package/dist/src/executor/filesystem-executor.js.map +1 -0
  38. package/dist/src/executor/http-executor.d.ts.map +1 -1
  39. package/dist/src/executor/http-executor.js +22 -2
  40. package/dist/src/executor/http-executor.js.map +1 -1
  41. package/dist/src/executor/index.d.ts +4 -0
  42. package/dist/src/executor/index.d.ts.map +1 -1
  43. package/dist/src/executor/index.js +9 -1
  44. package/dist/src/executor/index.js.map +1 -1
  45. package/dist/src/executor/shell-executor.d.ts +22 -0
  46. package/dist/src/executor/shell-executor.d.ts.map +1 -0
  47. package/dist/src/executor/shell-executor.js +119 -0
  48. package/dist/src/executor/shell-executor.js.map +1 -0
  49. package/dist/src/executor/sql-executor.d.ts +29 -0
  50. package/dist/src/executor/sql-executor.d.ts.map +1 -0
  51. package/dist/src/executor/sql-executor.js +114 -0
  52. package/dist/src/executor/sql-executor.js.map +1 -0
  53. package/dist/src/executor/websocket-executor.d.ts +26 -0
  54. package/dist/src/executor/websocket-executor.d.ts.map +1 -0
  55. package/dist/src/executor/websocket-executor.js +205 -0
  56. package/dist/src/executor/websocket-executor.js.map +1 -0
  57. package/dist/src/interceptor/index.d.ts +2 -0
  58. package/dist/src/interceptor/index.d.ts.map +1 -0
  59. package/dist/src/interceptor/index.js +6 -0
  60. package/dist/src/interceptor/index.js.map +1 -0
  61. package/dist/src/interceptor/provider-interceptor.d.ts +36 -0
  62. package/dist/src/interceptor/provider-interceptor.d.ts.map +1 -0
  63. package/dist/src/interceptor/provider-interceptor.js +302 -0
  64. package/dist/src/interceptor/provider-interceptor.js.map +1 -0
  65. package/dist/src/mcp/auth-verifier.d.ts.map +1 -1
  66. package/dist/src/mcp/auth-verifier.js +3 -2
  67. package/dist/src/mcp/auth-verifier.js.map +1 -1
  68. package/dist/src/mcp/bridge.d.ts +14 -10
  69. package/dist/src/mcp/bridge.d.ts.map +1 -1
  70. package/dist/src/mcp/bridge.js +51 -227
  71. package/dist/src/mcp/bridge.js.map +1 -1
  72. package/dist/src/mcp/http-transport.d.ts +2 -0
  73. package/dist/src/mcp/http-transport.d.ts.map +1 -1
  74. package/dist/src/mcp/http-transport.js +117 -66
  75. package/dist/src/mcp/http-transport.js.map +1 -1
  76. package/dist/src/mcp/internal-auth.d.ts +13 -0
  77. package/dist/src/mcp/internal-auth.d.ts.map +1 -0
  78. package/dist/src/mcp/internal-auth.js +12 -0
  79. package/dist/src/mcp/internal-auth.js.map +1 -0
  80. package/dist/src/mcp/tool-definitions.d.ts +41 -0
  81. package/dist/src/mcp/tool-definitions.d.ts.map +1 -0
  82. package/dist/src/mcp/tool-definitions.js +491 -0
  83. package/dist/src/mcp/tool-definitions.js.map +1 -0
  84. package/dist/src/middleware/auth.js.map +1 -1
  85. package/dist/src/middleware/session.js.map +1 -1
  86. package/dist/src/middleware/validate.d.ts +8 -0
  87. package/dist/src/middleware/validate.d.ts.map +1 -1
  88. package/dist/src/middleware/validate.js +45 -0
  89. package/dist/src/middleware/validate.js.map +1 -1
  90. package/dist/src/policy/engine.d.ts +4 -0
  91. package/dist/src/policy/engine.d.ts.map +1 -1
  92. package/dist/src/policy/engine.js +117 -0
  93. package/dist/src/policy/engine.js.map +1 -1
  94. package/dist/src/saas/routes.d.ts.map +1 -1
  95. package/dist/src/saas/routes.js +355 -22
  96. package/dist/src/saas/routes.js.map +1 -1
  97. package/dist/src/server/app.d.ts.map +1 -1
  98. package/dist/src/server/app.js +24 -3
  99. package/dist/src/server/app.js.map +1 -1
  100. package/dist/src/server/gateway.d.ts.map +1 -1
  101. package/dist/src/server/gateway.js +17 -0
  102. package/dist/src/server/gateway.js.map +1 -1
  103. package/dist/src/server/index.d.ts.map +1 -1
  104. package/dist/src/server/index.js +18 -0
  105. package/dist/src/server/index.js.map +1 -1
  106. package/dist/src/storage/interfaces.d.ts +14 -3
  107. package/dist/src/storage/interfaces.d.ts.map +1 -1
  108. package/dist/src/storage/memory.d.ts +2 -0
  109. package/dist/src/storage/memory.d.ts.map +1 -1
  110. package/dist/src/storage/memory.js +6 -0
  111. package/dist/src/storage/memory.js.map +1 -1
  112. package/dist/src/storage/postgres.d.ts +5 -0
  113. package/dist/src/storage/postgres.d.ts.map +1 -1
  114. package/dist/src/storage/postgres.js +16 -0
  115. package/dist/src/storage/postgres.js.map +1 -1
  116. package/dist/src/storage/redis.d.ts +10 -0
  117. package/dist/src/storage/redis.d.ts.map +1 -1
  118. package/dist/src/storage/redis.js +65 -0
  119. package/dist/src/storage/redis.js.map +1 -1
  120. package/dist/src/types/budget.d.ts +4 -0
  121. package/dist/src/types/budget.d.ts.map +1 -1
  122. package/dist/src/types/config.d.ts +58 -0
  123. package/dist/src/types/config.d.ts.map +1 -1
  124. package/dist/src/types/events.d.ts +1 -0
  125. package/dist/src/types/events.d.ts.map +1 -1
  126. package/dist/src/types/policy.d.ts +11 -1
  127. package/dist/src/types/policy.d.ts.map +1 -1
  128. package/dist/src/types/tool-result.d.ts +11 -0
  129. package/dist/src/types/tool-result.d.ts.map +1 -1
  130. package/dist/tests/unit/app-routes.test.d.ts +2 -0
  131. package/dist/tests/unit/app-routes.test.d.ts.map +1 -0
  132. package/dist/tests/unit/app-routes.test.js +715 -0
  133. package/dist/tests/unit/app-routes.test.js.map +1 -0
  134. package/dist/tests/unit/audit-logger.test.js +105 -0
  135. package/dist/tests/unit/audit-logger.test.js.map +1 -1
  136. package/dist/tests/unit/auth-providers.test.d.ts +2 -0
  137. package/dist/tests/unit/auth-providers.test.d.ts.map +1 -0
  138. package/dist/tests/unit/auth-providers.test.js +279 -0
  139. package/dist/tests/unit/auth-providers.test.js.map +1 -0
  140. package/dist/tests/unit/auth-routes-extended.test.d.ts +2 -0
  141. package/dist/tests/unit/auth-routes-extended.test.d.ts.map +1 -0
  142. package/dist/tests/unit/auth-routes-extended.test.js +993 -0
  143. package/dist/tests/unit/auth-routes-extended.test.js.map +1 -0
  144. package/dist/tests/unit/auth-verifier.test.d.ts +2 -0
  145. package/dist/tests/unit/auth-verifier.test.d.ts.map +1 -0
  146. package/dist/tests/unit/auth-verifier.test.js +505 -0
  147. package/dist/tests/unit/auth-verifier.test.js.map +1 -0
  148. package/dist/tests/unit/billing-routes.test.d.ts +2 -0
  149. package/dist/tests/unit/billing-routes.test.d.ts.map +1 -0
  150. package/dist/tests/unit/billing-routes.test.js +432 -0
  151. package/dist/tests/unit/billing-routes.test.js.map +1 -0
  152. package/dist/tests/unit/config-defaults.test.d.ts +2 -0
  153. package/dist/tests/unit/config-defaults.test.d.ts.map +1 -0
  154. package/dist/tests/unit/config-defaults.test.js +119 -0
  155. package/dist/tests/unit/config-defaults.test.js.map +1 -0
  156. package/dist/tests/unit/defaults.test.js +0 -10
  157. package/dist/tests/unit/defaults.test.js.map +1 -1
  158. package/dist/tests/unit/filesystem-executor.test.d.ts +2 -0
  159. package/dist/tests/unit/filesystem-executor.test.d.ts.map +1 -0
  160. package/dist/tests/unit/filesystem-executor.test.js +280 -0
  161. package/dist/tests/unit/filesystem-executor.test.js.map +1 -0
  162. package/dist/tests/unit/gateway-branches.test.d.ts +2 -0
  163. package/dist/tests/unit/gateway-branches.test.d.ts.map +1 -0
  164. package/dist/tests/unit/gateway-branches.test.js +1039 -0
  165. package/dist/tests/unit/gateway-branches.test.js.map +1 -0
  166. package/dist/tests/unit/http-executor-branches.test.d.ts +2 -0
  167. package/dist/tests/unit/http-executor-branches.test.d.ts.map +1 -0
  168. package/dist/tests/unit/http-executor-branches.test.js +495 -0
  169. package/dist/tests/unit/http-executor-branches.test.js.map +1 -0
  170. package/dist/tests/unit/logger.test.d.ts +2 -0
  171. package/dist/tests/unit/logger.test.d.ts.map +1 -0
  172. package/dist/tests/unit/logger.test.js +97 -0
  173. package/dist/tests/unit/logger.test.js.map +1 -0
  174. package/dist/tests/unit/mcp-internal-auth.test.d.ts +2 -0
  175. package/dist/tests/unit/mcp-internal-auth.test.d.ts.map +1 -0
  176. package/dist/tests/unit/mcp-internal-auth.test.js +445 -0
  177. package/dist/tests/unit/mcp-internal-auth.test.js.map +1 -0
  178. package/dist/tests/unit/metrics.test.js +102 -0
  179. package/dist/tests/unit/metrics.test.js.map +1 -1
  180. package/dist/tests/unit/model-pricing.test.d.ts +2 -0
  181. package/dist/tests/unit/model-pricing.test.d.ts.map +1 -0
  182. package/dist/tests/unit/model-pricing.test.js +87 -0
  183. package/dist/tests/unit/model-pricing.test.js.map +1 -0
  184. package/dist/tests/unit/oauth-stores.test.d.ts +2 -0
  185. package/dist/tests/unit/oauth-stores.test.d.ts.map +1 -0
  186. package/dist/tests/unit/oauth-stores.test.js +260 -0
  187. package/dist/tests/unit/oauth-stores.test.js.map +1 -0
  188. package/dist/tests/unit/policy-engine.test.js +466 -0
  189. package/dist/tests/unit/policy-engine.test.js.map +1 -1
  190. package/dist/tests/unit/provider-interceptor.test.d.ts +2 -0
  191. package/dist/tests/unit/provider-interceptor.test.d.ts.map +1 -0
  192. package/dist/tests/unit/provider-interceptor.test.js +472 -0
  193. package/dist/tests/unit/provider-interceptor.test.js.map +1 -0
  194. package/dist/tests/unit/saas-routes-branches.test.d.ts +2 -0
  195. package/dist/tests/unit/saas-routes-branches.test.d.ts.map +1 -0
  196. package/dist/tests/unit/saas-routes-branches.test.js +2165 -0
  197. package/dist/tests/unit/saas-routes-branches.test.js.map +1 -0
  198. package/dist/tests/unit/saas-routes-crud.test.d.ts +2 -0
  199. package/dist/tests/unit/saas-routes-crud.test.d.ts.map +1 -0
  200. package/dist/tests/unit/saas-routes-crud.test.js +332 -0
  201. package/dist/tests/unit/saas-routes-crud.test.js.map +1 -0
  202. package/dist/tests/unit/saas-routes-data.test.d.ts +2 -0
  203. package/dist/tests/unit/saas-routes-data.test.d.ts.map +1 -0
  204. package/dist/tests/unit/saas-routes-data.test.js +405 -0
  205. package/dist/tests/unit/saas-routes-data.test.js.map +1 -0
  206. package/dist/tests/unit/saas-routes.test.js +3 -3
  207. package/dist/tests/unit/saas-routes.test.js.map +1 -1
  208. package/dist/tests/unit/shell-executor.test.d.ts +2 -0
  209. package/dist/tests/unit/shell-executor.test.d.ts.map +1 -0
  210. package/dist/tests/unit/shell-executor.test.js +145 -0
  211. package/dist/tests/unit/shell-executor.test.js.map +1 -0
  212. package/dist/tests/unit/sql-executor.test.d.ts +2 -0
  213. package/dist/tests/unit/sql-executor.test.d.ts.map +1 -0
  214. package/dist/tests/unit/sql-executor.test.js +177 -0
  215. package/dist/tests/unit/sql-executor.test.js.map +1 -0
  216. package/dist/tests/unit/stream-proxy.test.d.ts +2 -0
  217. package/dist/tests/unit/stream-proxy.test.d.ts.map +1 -0
  218. package/dist/tests/unit/stream-proxy.test.js +147 -0
  219. package/dist/tests/unit/stream-proxy.test.js.map +1 -0
  220. package/dist/tests/unit/tool-definitions.test.d.ts +2 -0
  221. package/dist/tests/unit/tool-definitions.test.d.ts.map +1 -0
  222. package/dist/tests/unit/tool-definitions.test.js +184 -0
  223. package/dist/tests/unit/tool-definitions.test.js.map +1 -0
  224. package/dist/tests/unit/usage-extractor.test.js +140 -0
  225. package/dist/tests/unit/usage-extractor.test.js.map +1 -1
  226. package/dist/tests/unit/webhook-handler.test.d.ts +2 -0
  227. package/dist/tests/unit/webhook-handler.test.d.ts.map +1 -0
  228. package/dist/tests/unit/webhook-handler.test.js +453 -0
  229. package/dist/tests/unit/webhook-handler.test.js.map +1 -0
  230. package/dist/tests/unit/webhook-routes.test.d.ts +2 -0
  231. package/dist/tests/unit/webhook-routes.test.d.ts.map +1 -0
  232. package/dist/tests/unit/webhook-routes.test.js +69 -0
  233. package/dist/tests/unit/webhook-routes.test.js.map +1 -0
  234. package/dist/tests/unit/websocket-executor.test.d.ts +2 -0
  235. package/dist/tests/unit/websocket-executor.test.d.ts.map +1 -0
  236. package/dist/tests/unit/websocket-executor.test.js +121 -0
  237. package/dist/tests/unit/websocket-executor.test.js.map +1 -0
  238. package/package.json +8 -2
  239. package/policy-packs/demo_fail.yaml +41 -0
  240. package/policy-packs/full_tools.yaml +136 -0
  241. package/src/admin/index.ts +1 -0
  242. package/src/admin/routes.ts +509 -0
  243. package/src/admin/templates.ts +572 -0
  244. package/src/anomaly/detector.ts +730 -0
  245. package/src/anomaly/index.ts +1 -0
  246. package/src/approval/manager.ts +569 -0
  247. package/src/approval/webhook.ts +133 -0
  248. package/src/audit/logger.ts +490 -0
  249. package/src/auth/index.ts +5 -0
  250. package/src/auth/password.ts +21 -0
  251. package/src/auth/pkce.ts +22 -0
  252. package/src/auth/providers.ts +208 -0
  253. package/src/auth/routes.ts +561 -0
  254. package/src/auth/session.ts +84 -0
  255. package/src/billing/index.ts +6 -0
  256. package/src/billing/plan-enforcer.ts +135 -0
  257. package/src/billing/routes.ts +229 -0
  258. package/src/billing/stripe-client.ts +58 -0
  259. package/src/billing/webhook-handler.ts +182 -0
  260. package/src/billing/webhook-routes.ts +28 -0
  261. package/src/budget/manager.ts +679 -0
  262. package/src/budget/model-pricing.ts +119 -0
  263. package/src/budget/usage-extractor.ts +214 -0
  264. package/src/cli.ts +91 -0
  265. package/src/config/defaults.ts +261 -0
  266. package/src/config/validate.ts +88 -0
  267. package/src/dlp/composite-scanner.ts +213 -0
  268. package/src/dlp/index.ts +9 -0
  269. package/src/dlp/interfaces.ts +34 -0
  270. package/src/dlp/patterns.ts +30 -0
  271. package/src/dlp/prompt-injection-backend.ts +181 -0
  272. package/src/dlp/prompt-injection-patterns.ts +302 -0
  273. package/src/dlp/regex-backend.ts +181 -0
  274. package/src/dlp/scanner.ts +502 -0
  275. package/src/dlp/text-normalizer.ts +225 -0
  276. package/src/dlp/tool-patterns.ts +35 -0
  277. package/src/dlp/trufflehog-backend.ts +190 -0
  278. package/src/executor/filesystem-executor.ts +196 -0
  279. package/src/executor/http-executor.ts +349 -0
  280. package/src/executor/index.ts +9 -0
  281. package/src/executor/interfaces.ts +11 -0
  282. package/src/executor/noop-executor.ts +23 -0
  283. package/src/executor/registry.ts +64 -0
  284. package/src/executor/shell-executor.ts +148 -0
  285. package/src/executor/slack-executor.ts +176 -0
  286. package/src/executor/sql-executor.ts +146 -0
  287. package/src/executor/websocket-executor.ts +211 -0
  288. package/src/index.ts +24 -0
  289. package/src/interceptor/index.ts +1 -0
  290. package/src/interceptor/provider-interceptor.ts +315 -0
  291. package/src/mcp/auth-verifier.ts +152 -0
  292. package/src/mcp/bridge.ts +703 -0
  293. package/src/mcp/http-transport.ts +698 -0
  294. package/src/mcp/index.ts +9 -0
  295. package/src/mcp/internal-auth.ts +14 -0
  296. package/src/mcp/oauth-pages.ts +139 -0
  297. package/src/mcp/oauth-postgres-stores.ts +278 -0
  298. package/src/mcp/oauth-provider.ts +536 -0
  299. package/src/mcp/oauth-stores.ts +202 -0
  300. package/src/mcp/server.ts +55 -0
  301. package/src/mcp/tool-definitions.ts +562 -0
  302. package/src/metrics/collector.ts +357 -0
  303. package/src/metrics/index.ts +1 -0
  304. package/src/middleware/auth.ts +814 -0
  305. package/src/middleware/session.ts +85 -0
  306. package/src/middleware/validate.ts +130 -0
  307. package/src/policy/engine.ts +815 -0
  308. package/src/policy/index.ts +2 -0
  309. package/src/policy/opa-engine.ts +829 -0
  310. package/src/proxy/forward-proxy.ts +649 -0
  311. package/src/proxy/index.ts +1 -0
  312. package/src/ratelimit/limiter.ts +196 -0
  313. package/src/replay/engine.ts +142 -0
  314. package/src/replay/index.ts +1 -0
  315. package/src/saas/index.ts +1 -0
  316. package/src/saas/routes.ts +2178 -0
  317. package/src/server/app.ts +985 -0
  318. package/src/server/errors.ts +49 -0
  319. package/src/server/gateway.ts +1130 -0
  320. package/src/server/index.ts +307 -0
  321. package/src/server/logger.ts +255 -0
  322. package/src/server/stream-proxy.ts +202 -0
  323. package/src/storage/file-persistence.ts +315 -0
  324. package/src/storage/index.ts +4 -0
  325. package/src/storage/interfaces.ts +287 -0
  326. package/src/storage/memory.ts +686 -0
  327. package/src/storage/postgres.ts +1831 -0
  328. package/src/storage/redis.ts +835 -0
  329. package/src/tracing/index.ts +1 -0
  330. package/src/tracing/provider.ts +100 -0
  331. package/src/trust/calculator.ts +141 -0
  332. package/src/trust/index.ts +7 -0
  333. package/src/types/budget.ts +36 -0
  334. package/src/types/config.ts +278 -0
  335. package/src/types/events.ts +41 -0
  336. package/src/types/express.d.ts +14 -0
  337. package/src/types/index.ts +7 -0
  338. package/src/types/policy.ts +83 -0
  339. package/src/types/stripe-config.ts +11 -0
  340. package/src/types/subscription.ts +59 -0
  341. package/src/types/tool-call.ts +47 -0
  342. package/src/types/tool-result.ts +82 -0
  343. package/src/types/user.ts +125 -0
  344. package/tsconfig.json +24 -0
@@ -0,0 +1,307 @@
1
+ import { createApp, SaaSStores } from './app';
2
+ import { DEFAULT_CONFIG } from '../config/defaults';
3
+ import { GatewayConfig } from '../types/config';
4
+ import { validateConfig } from '../config/validate';
5
+ import { log as devLog } from './logger';
6
+
7
+ export async function startServer(): Promise<void> {
8
+ const config: GatewayConfig = { ...DEFAULT_CONFIG };
9
+
10
+ // Wire up OpenTelemetry tracing from env vars
11
+ if (process.env.OTEL_EXPORTER_OTLP_ENDPOINT) {
12
+ config.tracing = {
13
+ enabled: true,
14
+ service_name: process.env.OTEL_SERVICE_NAME || 'palaryn',
15
+ otlp_endpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
16
+ environment: process.env.NODE_ENV || 'development',
17
+ };
18
+ }
19
+
20
+ // Pre-initialize Postgres SaaS stores when DATABASE_URL is set (persists user
21
+ // data, sessions, workspaces, and API keys across restarts)
22
+ let externalSaaSStores: Partial<SaaSStores> | undefined;
23
+ if (process.env.DATABASE_URL) {
24
+ try {
25
+ const { Pool } = await import('pg');
26
+ const pool = new Pool({ connectionString: process.env.DATABASE_URL });
27
+ const { initPostgresStorage, createPostgresSaaSStores } = await import('../storage/postgres');
28
+ const { PostgresOAuthClientsStore, PostgresOAuthTokenStore } = await import('../mcp/oauth-postgres-stores');
29
+ await initPostgresStorage(pool);
30
+
31
+ // Create MCP OAuth Postgres tables
32
+ await PostgresOAuthClientsStore.createTables(pool);
33
+ await PostgresOAuthTokenStore.createTables(pool);
34
+
35
+ const pgSaaSStores = createPostgresSaaSStores(pool);
36
+ const mcpOAuthClientsStore = new PostgresOAuthClientsStore(pool);
37
+ const mcpOAuthTokenStore = new PostgresOAuthTokenStore(pool);
38
+
39
+ // Hydrate SaaS stores
40
+ await Promise.all([
41
+ pgSaaSStores.userStore.hydrate(),
42
+ pgSaaSStores.oauthAccountStore.hydrate(),
43
+ pgSaaSStores.workspaceStore.hydrate(),
44
+ pgSaaSStores.workspaceMemberStore.hydrate(),
45
+ pgSaaSStores.sessionStore.hydrate(),
46
+ pgSaaSStores.userApiKeyStore.hydrate(),
47
+ pgSaaSStores.subscriptionStore.hydrate(),
48
+ pgSaaSStores.rateLimitConfigStore.hydrate(),
49
+ pgSaaSStores.budgetConfigStore.hydrate(),
50
+ pgSaaSStores.policyStore.hydrate(),
51
+ mcpOAuthClientsStore.hydrate(),
52
+ mcpOAuthTokenStore.hydrate(),
53
+ ]);
54
+
55
+ externalSaaSStores = {
56
+ ...pgSaaSStores,
57
+ mcpOAuthClientsStore,
58
+ mcpOAuthTokenStore,
59
+ };
60
+ console.log('PostgreSQL SaaS stores initialized (incl. MCP OAuth)');
61
+ } catch (err) {
62
+ console.warn('PostgreSQL SaaS store init failed, using in-memory:', (err as Error).message);
63
+ }
64
+ }
65
+
66
+ // Validate configuration before startup
67
+ const configValidation = validateConfig(config);
68
+ for (const issue of configValidation.issues) {
69
+ if (issue.level === 'fatal') {
70
+ console.error(`[fatal] ${issue.message}`);
71
+ } else {
72
+ console.warn(`[warn] ${issue.message}`);
73
+ }
74
+ }
75
+ if (!configValidation.valid) {
76
+ console.error('Configuration validation failed. Fix the fatal errors above and restart.');
77
+ process.exit(1);
78
+ }
79
+
80
+ const { app, gateway, tracer, metrics, healthChecks, saasStores, proxyServer } = createApp(config, externalSaaSStores);
81
+
82
+ // Startup warnings
83
+ const isProduction = process.env.NODE_ENV === 'production';
84
+ if (isProduction && !process.env.REDIS_URL && !process.env.DATABASE_URL) {
85
+ console.warn('[warn] Running in production without REDIS_URL or DATABASE_URL. Data is stored in-memory and will be lost on restart.');
86
+ }
87
+ if (isProduction && config.approval.token_secret === 'palaryn-dev-approval-secret') {
88
+ console.warn('[warn] Using default approval secret in production. Set APPROVAL_SECRET env var.');
89
+ }
90
+ const policyPack = gateway.getCurrentPolicy();
91
+ if (policyPack.rules.length === 0) {
92
+ console.warn('[warn] Policy pack has no rules. All requests will be handled by default_effect (' + config.policy.default_effect + ').');
93
+ }
94
+
95
+ // Wire up Redis if REDIS_URL is set
96
+ if (process.env.REDIS_URL) {
97
+ try {
98
+ const Redis = (await import('ioredis')).default;
99
+ const redis = new Redis(process.env.REDIS_URL);
100
+ const { createRedisStores } = await import('../storage/redis');
101
+ const stores = createRedisStores(redis, 'palaryn:');
102
+
103
+ // Inject Redis stores into Gateway (replaces default in-memory stores)
104
+ gateway.setStores({
105
+ idempotencyStore: stores.idempotencyStore,
106
+ rateLimitStore: stores.rateLimitStore,
107
+ auditStore: stores.auditStore,
108
+ budgetStore: stores.budgetStore,
109
+ approvalStore: stores.approvalStore,
110
+ });
111
+
112
+ // Hydrate stores from Redis (restore state after restart)
113
+ await Promise.all([
114
+ stores.budgetStore.hydrate(),
115
+ stores.auditStore.hydrate(),
116
+ stores.approvalStore.hydrate(),
117
+ ]);
118
+
119
+ // Populate AuditLogger in-memory buffer from hydrated Redis store
120
+ gateway.getAuditLogger().hydrateFromStore();
121
+
122
+ // Populate BudgetManager in-memory maps from hydrated Redis store
123
+ gateway.getBudgetManager().hydrateFromStore();
124
+
125
+ // Hydrate RateLimitStore local windows from Redis
126
+ if (stores.rateLimitStore.hydrateAll) {
127
+ await stores.rateLimitStore.hydrateAll();
128
+ }
129
+
130
+ // Register Redis health check
131
+ healthChecks.push({
132
+ name: 'redis',
133
+ check: async () => {
134
+ try {
135
+ const result = await redis.ping();
136
+ return { status: result === 'PONG' ? 'ok' : 'unhealthy', message: result };
137
+ } catch (err) {
138
+ return { status: 'unhealthy', message: (err as Error).message };
139
+ }
140
+ },
141
+ });
142
+
143
+ console.log('Redis connected:', process.env.REDIS_URL);
144
+ } catch (err) {
145
+ console.warn('Redis connection failed, using in-memory stores:', (err as Error).message);
146
+ }
147
+ }
148
+
149
+ // Wire up PostgreSQL if DATABASE_URL is set
150
+ if (process.env.DATABASE_URL) {
151
+ try {
152
+ const { Pool } = await import('pg');
153
+ const pool = new Pool({ connectionString: process.env.DATABASE_URL });
154
+ const { initPostgresStorage, createPostgresStores } = await import('../storage/postgres');
155
+ await initPostgresStorage(pool);
156
+
157
+ // Inject Postgres stores if Redis was not already connected (Redis takes priority)
158
+ if (!process.env.REDIS_URL) {
159
+ const pgStores = createPostgresStores(pool);
160
+ gateway.setStores({
161
+ idempotencyStore: pgStores.idempotencyStore,
162
+ rateLimitStore: pgStores.rateLimitStore,
163
+ auditStore: pgStores.auditStore,
164
+ budgetStore: pgStores.budgetStore,
165
+ approvalStore: pgStores.approvalStore,
166
+ });
167
+
168
+ // Hydrate Postgres stores (restore state after restart)
169
+ await Promise.all([
170
+ pgStores.budgetStore.hydrate(),
171
+ pgStores.auditStore.hydrate(),
172
+ pgStores.approvalStore.hydrate(),
173
+ ]);
174
+
175
+ // Populate AuditLogger in-memory buffer from hydrated Postgres store
176
+ gateway.getAuditLogger().hydrateFromStore();
177
+
178
+ // Populate BudgetManager in-memory maps from hydrated Postgres store
179
+ gateway.getBudgetManager().hydrateFromStore();
180
+ }
181
+
182
+ // Register Postgres health check
183
+ healthChecks.push({
184
+ name: 'postgres',
185
+ check: async () => {
186
+ try {
187
+ await pool.query('SELECT 1');
188
+ return { status: 'ok' };
189
+ } catch (err) {
190
+ return { status: 'unhealthy', message: (err as Error).message };
191
+ }
192
+ },
193
+ });
194
+
195
+ console.log('PostgreSQL connected:', process.env.DATABASE_URL.replace(/:[^:@]*@/, ':***@'));
196
+ } catch (err) {
197
+ console.warn('PostgreSQL connection failed:', (err as Error).message);
198
+ }
199
+ }
200
+
201
+ const server = app.listen(config.port, config.host, () => {
202
+ devLog.serverStart(config.host, config.port, {
203
+ 'Policy': config.policy.pack_path,
204
+ 'Auth': config.auth.enabled,
205
+ 'DLP': config.dlp.enabled,
206
+ 'Audit': config.audit.enabled,
207
+ 'Tracing': config.tracing?.enabled || false,
208
+ 'Redis': !!process.env.REDIS_URL,
209
+ 'PostgreSQL': !!process.env.DATABASE_URL,
210
+ 'OAuth': config.mcp_oauth?.enabled || config.oauth?.enabled || false,
211
+ 'Frontend': config.frontend?.enabled || false,
212
+ 'Proxy': config.proxy?.enabled || false,
213
+ });
214
+ });
215
+
216
+ // S7: Track active requests for graceful shutdown
217
+ let activeRequests = 0;
218
+ server.on('request', (_req: any, res: any) => {
219
+ activeRequests++;
220
+ const decrement = () => { activeRequests = Math.max(0, activeRequests - 1); };
221
+ res.on('finish', decrement);
222
+ res.on('close', decrement);
223
+ });
224
+
225
+ // Start forward proxy server alongside Express
226
+ if (proxyServer && config.proxy?.enabled) {
227
+ const proxyPort = config.proxy.port || 3128;
228
+ proxyServer.listen(proxyPort, () => {
229
+ console.log(`Forward proxy listening on :${proxyPort}`);
230
+ });
231
+ }
232
+
233
+ // Graceful shutdown
234
+ async function gracefulShutdown(signal: string): Promise<void> {
235
+ devLog.serverShutdown(signal);
236
+
237
+ // Force exit after timeout (configurable via SHUTDOWN_TIMEOUT_MS env var)
238
+ const shutdownTimeoutMs = parseInt(process.env.SHUTDOWN_TIMEOUT_MS || '', 10) || 10000;
239
+ const forceTimer = setTimeout(() => {
240
+ console.error('Forced shutdown after timeout');
241
+ process.exit(1);
242
+ }, shutdownTimeoutMs);
243
+ forceTimer.unref();
244
+
245
+ // 1. Stop accepting new connections
246
+ if (proxyServer) {
247
+ await new Promise<void>((resolve) => {
248
+ proxyServer!.close(() => {
249
+ console.log('Proxy server closed');
250
+ resolve();
251
+ });
252
+ });
253
+ }
254
+ server.close(() => {
255
+ console.log('HTTP server closed (no new connections)');
256
+ });
257
+
258
+ // S7: Wait for active requests to drain (up to 30 seconds)
259
+ const drainTimeout = 30_000;
260
+ const drainStart = Date.now();
261
+ while (activeRequests > 0 && (Date.now() - drainStart) < drainTimeout) {
262
+ await new Promise<void>((resolve) => setTimeout(resolve, 100));
263
+ }
264
+ if (activeRequests > 0) {
265
+ console.warn(`Shutdown: ${activeRequests} active requests still pending after ${drainTimeout}ms drain timeout, force-closing`);
266
+ } else {
267
+ console.log('All active requests drained');
268
+ }
269
+
270
+ // 2. Shut down gateway (audit logger, idempotency store, rate limiter)
271
+ await gateway.shutdown();
272
+ console.log('Gateway shut down');
273
+
274
+ // 3. Flush OTel traces
275
+ if (tracer) {
276
+ await tracer.shutdown();
277
+ console.log('Tracer flushed and shut down');
278
+ }
279
+
280
+ // 4. Flush metrics
281
+ metrics.reset();
282
+ console.log('Metrics reset');
283
+
284
+ console.log('Shutdown complete');
285
+ process.exit(0);
286
+ }
287
+
288
+ let shuttingDown = false;
289
+ for (const signal of ['SIGTERM', 'SIGINT']) {
290
+ process.on(signal, () => {
291
+ if (shuttingDown) return;
292
+ shuttingDown = true;
293
+ gracefulShutdown(signal).catch((err) => {
294
+ console.error('Shutdown error:', err);
295
+ process.exit(1);
296
+ });
297
+ });
298
+ }
299
+ }
300
+
301
+ // Direct execution (node dist/src/server/index.js)
302
+ if (require.main === module) {
303
+ startServer().catch((err) => {
304
+ console.error('Failed to start gateway:', err);
305
+ process.exit(1);
306
+ });
307
+ }
@@ -0,0 +1,255 @@
1
+ /**
2
+ * Pretty console logger for Palaryn dev mode.
3
+ * Color-coded pipeline steps with box-drawing characters.
4
+ *
5
+ * Also exports a Winston `logger` for structured logging (JSON in production,
6
+ * colorized console in development).
7
+ */
8
+
9
+ import winston from 'winston';
10
+
11
+ const isProduction = process.env.NODE_ENV === 'production';
12
+
13
+ export const logger = winston.createLogger({
14
+ level: process.env.LOG_LEVEL || (isProduction ? 'info' : 'debug'),
15
+ format: isProduction
16
+ ? winston.format.combine(
17
+ winston.format.timestamp(),
18
+ winston.format.json(),
19
+ )
20
+ : winston.format.combine(
21
+ winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss.SSS' }),
22
+ winston.format.colorize(),
23
+ winston.format.printf(({ timestamp, level, message, ...meta }) => {
24
+ const metaStr = Object.keys(meta).length > 0 ? ' ' + JSON.stringify(meta) : '';
25
+ return `${timestamp} ${level}: ${message}${metaStr}`;
26
+ }),
27
+ ),
28
+ transports: [
29
+ new winston.transports.Console(),
30
+ ],
31
+ });
32
+
33
+ const RESET = '\x1b[0m';
34
+ const BOLD = '\x1b[1m';
35
+ const DIM = '\x1b[2m';
36
+
37
+ const FG = {
38
+ black: '\x1b[30m',
39
+ red: '\x1b[31m',
40
+ green: '\x1b[32m',
41
+ yellow: '\x1b[33m',
42
+ blue: '\x1b[34m',
43
+ magenta: '\x1b[35m',
44
+ cyan: '\x1b[36m',
45
+ white: '\x1b[37m',
46
+ gray: '\x1b[90m',
47
+ };
48
+
49
+ const BG = {
50
+ red: '\x1b[41m',
51
+ green: '\x1b[42m',
52
+ yellow: '\x1b[43m',
53
+ blue: '\x1b[44m',
54
+ magenta: '\x1b[45m',
55
+ cyan: '\x1b[46m',
56
+ white: '\x1b[47m',
57
+ };
58
+
59
+ function timestamp(): string {
60
+ return new Date().toISOString().replace('T', ' ').replace('Z', '');
61
+ }
62
+
63
+ function tag(bg: string, fg: string, label: string): string {
64
+ return `${bg}${fg}${BOLD} ${label} ${RESET}`;
65
+ }
66
+
67
+ function indent(text: string, prefix: string = ' '): string {
68
+ return text.split('\n').map(l => prefix + l).join('\n');
69
+ }
70
+
71
+ function truncate(s: string, max: number = 200): string {
72
+ if (s.length <= max) return s;
73
+ return s.slice(0, max) + `${DIM}... (${s.length} chars)${RESET}`;
74
+ }
75
+
76
+ function jsonPretty(obj: unknown, maxDepth: number = 3): string {
77
+ try {
78
+ const s = JSON.stringify(obj, null, 2);
79
+ return truncate(s, 500);
80
+ } catch {
81
+ return String(obj);
82
+ }
83
+ }
84
+
85
+ export const log = {
86
+ // ── Request lifecycle ─────────────────────────────────────
87
+
88
+ request(method: string, path: string, ip: string, body?: unknown) {
89
+ const t = timestamp();
90
+ console.log('');
91
+ console.log(`${FG.gray}${t}${RESET}`);
92
+ console.log(`${tag(BG.blue, FG.white, 'REQUEST')} ${BOLD}${method}${RESET} ${FG.cyan}${path}${RESET} ${DIM}from ${ip}${RESET}`);
93
+ if (body && Object.keys(body as Record<string, unknown>).length > 0) {
94
+ const b = body as Record<string, unknown>;
95
+ if (b.tool) {
96
+ const tool = b.tool as Record<string, unknown>;
97
+ console.log(` ${FG.yellow}tool:${RESET} ${BOLD}${tool.name}${RESET} v${tool.version} ${DIM}[${tool.capability}]${RESET}`);
98
+ }
99
+ if (b.actor) {
100
+ const actor = b.actor as Record<string, unknown>;
101
+ console.log(` ${FG.yellow}actor:${RESET} ${actor.id} ${DIM}(${actor.type})${RESET}`);
102
+ }
103
+ if (b.args) {
104
+ const args = b.args as Record<string, unknown>;
105
+ if (args.method && args.url) {
106
+ console.log(` ${FG.yellow}target:${RESET} ${args.method} ${FG.cyan}${args.url}${RESET}`);
107
+ } else {
108
+ console.log(` ${FG.yellow}args:${RESET} ${DIM}${truncate(JSON.stringify(args), 120)}${RESET}`);
109
+ }
110
+ }
111
+ if (b.tool_call_id) {
112
+ console.log(` ${FG.yellow}call_id:${RESET} ${DIM}${b.tool_call_id}${RESET}`);
113
+ }
114
+ }
115
+ },
116
+
117
+ response(statusCode: number, durationMs: number, body?: unknown) {
118
+ const color = statusCode < 300 ? FG.green : statusCode < 400 ? FG.yellow : FG.red;
119
+ const bgColor = statusCode < 300 ? BG.green : statusCode < 400 ? BG.yellow : BG.red;
120
+ console.log(`${tag(bgColor, FG.white, 'RESPONSE')} ${color}${BOLD}${statusCode}${RESET} ${DIM}in ${durationMs}ms${RESET}`);
121
+ if (body) {
122
+ const b = body as Record<string, unknown>;
123
+ if (b.status) {
124
+ console.log(` ${FG.yellow}status:${RESET} ${b.status === 'ok' ? FG.green : FG.red}${b.status}${RESET}`);
125
+ }
126
+ if (b.error) {
127
+ console.log(` ${FG.red}error:${RESET} ${b.error}`);
128
+ }
129
+ }
130
+ console.log('');
131
+ },
132
+
133
+ // ── Pipeline steps ────────────────────────────────────────
134
+
135
+ pipelineStart(toolCallId: string, toolName: string) {
136
+ console.log(`${FG.gray} ┌─────────────────────────────────────────${RESET}`);
137
+ console.log(`${FG.gray} │${RESET} ${tag(BG.magenta, FG.white, 'PIPELINE')} ${BOLD}${toolName}${RESET} ${DIM}(${toolCallId})${RESET}`);
138
+ },
139
+
140
+ pipelineStep(icon: string, label: string, detail: string, color: string = FG.white) {
141
+ console.log(`${FG.gray} │${RESET} ${icon} ${BOLD}${color}${label}${RESET} ${detail}`);
142
+ },
143
+
144
+ pipelineEnd(status: string, durationMs: number) {
145
+ const color = status === 'ok' ? FG.green : status === 'blocked' ? FG.red : FG.yellow;
146
+ console.log(`${FG.gray} │${RESET} ${tag(status === 'ok' ? BG.green : status === 'blocked' ? BG.red : BG.yellow, FG.white, 'DONE')} ${color}${BOLD}${status}${RESET} ${DIM}(${durationMs}ms)${RESET}`);
147
+ console.log(`${FG.gray} └─────────────────────────────────────────${RESET}`);
148
+ },
149
+
150
+ // ── Specific pipeline stages ──────────────────────────────
151
+
152
+ idempotencyHit(toolCallId: string) {
153
+ this.pipelineStep('$', 'CACHE HIT', `${DIM}returning cached result for ${toolCallId}${RESET}`, FG.cyan);
154
+ },
155
+
156
+ idempotencyMiss() {
157
+ this.pipelineStep('$', 'CACHE', `${FG.green}miss${RESET} ${DIM}(new call)${RESET}`, FG.cyan);
158
+ },
159
+
160
+ rateLimit(allowed: boolean, current?: number, limit?: number, blockedBy?: string) {
161
+ if (allowed) {
162
+ this.pipelineStep('\u{1F6A6}', 'RATE LIMIT', `${FG.green}pass${RESET} ${DIM}(${current}/${limit})${RESET}`, FG.blue);
163
+ } else {
164
+ this.pipelineStep('\u{1F6A6}', 'RATE LIMIT', `${FG.red}BLOCKED${RESET} ${blockedBy}: ${current}/${limit}`, FG.red);
165
+ }
166
+ },
167
+
168
+ anomaly(alertCount: number, blocked: boolean) {
169
+ if (alertCount === 0) {
170
+ this.pipelineStep('\u{1F50D}', 'ANOMALY', `${FG.green}clean${RESET}`, FG.blue);
171
+ } else if (blocked) {
172
+ this.pipelineStep('\u{1F50D}', 'ANOMALY', `${FG.red}BLOCKED${RESET} (${alertCount} alerts)`, FG.red);
173
+ } else {
174
+ this.pipelineStep('\u{1F50D}', 'ANOMALY', `${FG.yellow}${alertCount} alert(s) flagged${RESET}`, FG.yellow);
175
+ }
176
+ },
177
+
178
+ policy(decision: string, ruleId?: string, reasons?: string[]) {
179
+ const color = decision === 'allow' ? FG.green : decision === 'deny' ? FG.red : decision === 'transform' ? FG.yellow : FG.magenta;
180
+ const icon = decision === 'allow' ? '\u{2705}' : decision === 'deny' ? '\u{274C}' : decision === 'transform' ? '\u{1F504}' : '\u{23F3}';
181
+ this.pipelineStep(icon, 'POLICY', `${color}${BOLD}${decision.toUpperCase()}${RESET} ${DIM}rule: ${ruleId || 'none'}${RESET}`, color);
182
+ if (reasons && reasons.length > 0) {
183
+ for (const r of reasons) {
184
+ console.log(`${FG.gray} │${RESET} ${DIM} -> ${r}${RESET}`);
185
+ }
186
+ }
187
+ },
188
+
189
+ dlp(phase: 'args' | 'output', detected: string[], severity: string, redactionCount: number) {
190
+ const label = phase === 'args' ? 'DLP ARGS' : 'DLP OUTPUT';
191
+ if (detected.length === 0) {
192
+ this.pipelineStep('\u{1F6E1}\u{FE0F}', label, `${FG.green}clean${RESET}`, FG.blue);
193
+ } else {
194
+ const sevColor = severity === 'high' ? FG.red : severity === 'medium' ? FG.yellow : FG.cyan;
195
+ this.pipelineStep('\u{1F6E1}\u{FE0F}', label, `${sevColor}${severity}${RESET} found: ${FG.yellow}${detected.join(', ')}${RESET} ${DIM}(${redactionCount} redactions)${RESET}`, sevColor);
196
+ }
197
+ },
198
+
199
+ budget(allowed: boolean, estimated: number, spent: number, remaining: number, reason?: string) {
200
+ if (allowed) {
201
+ this.pipelineStep('\u{1F4B0}', 'BUDGET', `${FG.green}pass${RESET} est:$${estimated.toFixed(4)} spent:$${spent.toFixed(4)} left:$${remaining.toFixed(4)}`, FG.green);
202
+ } else {
203
+ this.pipelineStep('\u{1F4B0}', 'BUDGET', `${FG.red}BLOCKED${RESET} ${reason}`, FG.red);
204
+ }
205
+ },
206
+
207
+ transform(transformations: Array<{ type: string; target: string }>) {
208
+ for (const t of transformations) {
209
+ this.pipelineStep('\u{1F504}', 'TRANSFORM', `${FG.yellow}${t.type}${RESET} -> ${t.target}`, FG.yellow);
210
+ }
211
+ },
212
+
213
+ executing(toolName: string, target?: string) {
214
+ const detail = target ? `${FG.cyan}${target}${RESET}` : '';
215
+ this.pipelineStep('\u{26A1}', 'EXECUTE', `${BOLD}${toolName}${RESET} ${detail}`, FG.magenta);
216
+ },
217
+
218
+ executed(httpStatus: number, durationMs: number) {
219
+ const color = httpStatus < 300 ? FG.green : httpStatus < 400 ? FG.yellow : FG.red;
220
+ this.pipelineStep('\u{26A1}', 'RESULT', `${color}HTTP ${httpStatus}${RESET} ${DIM}(${durationMs}ms)${RESET}`, color);
221
+ },
222
+
223
+ executionError(error: string) {
224
+ this.pipelineStep('\u{1F4A5}', 'ERROR', `${FG.red}${error}${RESET}`, FG.red);
225
+ },
226
+
227
+ // ── Server lifecycle ──────────────────────────────────────
228
+
229
+ serverStart(host: string, port: number, config: Record<string, unknown>) {
230
+ console.log('');
231
+ console.log(`${BOLD}${FG.cyan} ╔════════════════════════════════════════════╗${RESET}`);
232
+ console.log(`${BOLD}${FG.cyan} ║${RESET} ${BOLD}Palaryn Gateway${RESET} ${DIM}v0.1.0${RESET} ${BOLD}${FG.cyan}║${RESET}`);
233
+ console.log(`${BOLD}${FG.cyan} ╚════════════════════════════════════════════╝${RESET}`);
234
+ console.log('');
235
+ console.log(` ${FG.green}${BOLD}Server:${RESET} http://${host}:${port}`);
236
+ console.log(` ${FG.green}${BOLD}Admin:${RESET} http://${host}:${port}/admin`);
237
+ console.log(` ${FG.green}${BOLD}Health:${RESET} http://${host}:${port}/health`);
238
+ console.log(` ${FG.green}${BOLD}Metrics:${RESET} http://${host}:${port}/metrics`);
239
+ console.log('');
240
+ for (const [key, val] of Object.entries(config)) {
241
+ const display = typeof val === 'boolean'
242
+ ? (val ? `${FG.green}enabled${RESET}` : `${FG.gray}disabled${RESET}`)
243
+ : `${FG.white}${val}${RESET}`;
244
+ console.log(` ${DIM}${key}:${RESET} ${display}`);
245
+ }
246
+ console.log('');
247
+ console.log(`${DIM} Waiting for requests...${RESET}`);
248
+ console.log('');
249
+ },
250
+
251
+ serverShutdown(signal: string) {
252
+ console.log('');
253
+ console.log(`${tag(BG.red, FG.white, 'SHUTDOWN')} ${signal} received`);
254
+ },
255
+ };