jfl 0.2.3 → 0.2.5

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 (128) hide show
  1. package/README.md +399 -423
  2. package/dist/commands/agent.d.ts +7 -0
  3. package/dist/commands/agent.d.ts.map +1 -0
  4. package/dist/commands/agent.js +170 -0
  5. package/dist/commands/agent.js.map +1 -0
  6. package/dist/commands/context-hub.d.ts.map +1 -1
  7. package/dist/commands/context-hub.js +134 -2
  8. package/dist/commands/context-hub.js.map +1 -1
  9. package/dist/commands/digest.d.ts +6 -0
  10. package/dist/commands/digest.d.ts.map +1 -0
  11. package/dist/commands/digest.js +126 -0
  12. package/dist/commands/digest.js.map +1 -0
  13. package/dist/commands/doctor.d.ts +7 -0
  14. package/dist/commands/doctor.d.ts.map +1 -0
  15. package/dist/commands/doctor.js +236 -0
  16. package/dist/commands/doctor.js.map +1 -0
  17. package/dist/commands/flows.d.ts +7 -0
  18. package/dist/commands/flows.d.ts.map +1 -0
  19. package/dist/commands/flows.js +264 -0
  20. package/dist/commands/flows.js.map +1 -0
  21. package/dist/commands/hooks.d.ts +13 -0
  22. package/dist/commands/hooks.d.ts.map +1 -0
  23. package/dist/commands/hooks.js +304 -0
  24. package/dist/commands/hooks.js.map +1 -0
  25. package/dist/commands/improve.d.ts +11 -0
  26. package/dist/commands/improve.d.ts.map +1 -0
  27. package/dist/commands/improve.js +77 -0
  28. package/dist/commands/improve.js.map +1 -0
  29. package/dist/commands/scope.d.ts +7 -0
  30. package/dist/commands/scope.d.ts.map +1 -0
  31. package/dist/commands/scope.js +227 -0
  32. package/dist/commands/scope.js.map +1 -0
  33. package/dist/commands/service-validate.js +7 -1
  34. package/dist/commands/service-validate.js.map +1 -1
  35. package/dist/commands/session.d.ts.map +1 -1
  36. package/dist/commands/session.js +23 -0
  37. package/dist/commands/session.js.map +1 -1
  38. package/dist/commands/update.d.ts.map +1 -1
  39. package/dist/commands/update.js +6 -0
  40. package/dist/commands/update.js.map +1 -1
  41. package/dist/index.js +243 -46
  42. package/dist/index.js.map +1 -1
  43. package/dist/lib/agent-manifest.d.ts +35 -0
  44. package/dist/lib/agent-manifest.d.ts.map +1 -0
  45. package/dist/lib/agent-manifest.js +75 -0
  46. package/dist/lib/agent-manifest.js.map +1 -0
  47. package/dist/lib/flow-engine.d.ts +34 -0
  48. package/dist/lib/flow-engine.d.ts.map +1 -0
  49. package/dist/lib/flow-engine.js +366 -0
  50. package/dist/lib/flow-engine.js.map +1 -0
  51. package/dist/lib/hook-transformer.d.ts +11 -0
  52. package/dist/lib/hook-transformer.d.ts.map +1 -0
  53. package/dist/lib/hook-transformer.js +74 -0
  54. package/dist/lib/hook-transformer.js.map +1 -0
  55. package/dist/lib/kuva.d.ts +45 -0
  56. package/dist/lib/kuva.d.ts.map +1 -0
  57. package/dist/lib/kuva.js +131 -0
  58. package/dist/lib/kuva.js.map +1 -0
  59. package/dist/lib/map-event-bus.d.ts +2 -0
  60. package/dist/lib/map-event-bus.d.ts.map +1 -1
  61. package/dist/lib/map-event-bus.js +44 -4
  62. package/dist/lib/map-event-bus.js.map +1 -1
  63. package/dist/lib/memory-indexer.d.ts.map +1 -1
  64. package/dist/lib/memory-indexer.js +26 -2
  65. package/dist/lib/memory-indexer.js.map +1 -1
  66. package/dist/lib/model-pricing.d.ts +11 -0
  67. package/dist/lib/model-pricing.d.ts.map +1 -0
  68. package/dist/lib/model-pricing.js +27 -0
  69. package/dist/lib/model-pricing.js.map +1 -0
  70. package/dist/lib/peter-parker-bridge.d.ts +1 -0
  71. package/dist/lib/peter-parker-bridge.d.ts.map +1 -1
  72. package/dist/lib/peter-parker-bridge.js +29 -0
  73. package/dist/lib/peter-parker-bridge.js.map +1 -1
  74. package/dist/lib/service-gtm.d.ts +7 -0
  75. package/dist/lib/service-gtm.d.ts.map +1 -1
  76. package/dist/lib/service-gtm.js.map +1 -1
  77. package/dist/lib/stratus-client.d.ts +1 -0
  78. package/dist/lib/stratus-client.d.ts.map +1 -1
  79. package/dist/lib/stratus-client.js +33 -2
  80. package/dist/lib/stratus-client.js.map +1 -1
  81. package/dist/lib/stratus-rollout-test.d.ts +10 -0
  82. package/dist/lib/stratus-rollout-test.d.ts.map +1 -0
  83. package/dist/lib/stratus-rollout-test.js +412 -0
  84. package/dist/lib/stratus-rollout-test.js.map +1 -0
  85. package/dist/lib/telemetry-digest.d.ts +10 -0
  86. package/dist/lib/telemetry-digest.d.ts.map +1 -0
  87. package/dist/lib/telemetry-digest.js +359 -0
  88. package/dist/lib/telemetry-digest.js.map +1 -0
  89. package/dist/lib/telemetry.d.ts +35 -0
  90. package/dist/lib/telemetry.d.ts.map +1 -0
  91. package/dist/lib/telemetry.js +320 -0
  92. package/dist/lib/telemetry.js.map +1 -0
  93. package/dist/lib/training-tuples.d.ts +33 -0
  94. package/dist/lib/training-tuples.d.ts.map +1 -0
  95. package/dist/lib/training-tuples.js +273 -0
  96. package/dist/lib/training-tuples.js.map +1 -0
  97. package/dist/mcp/context-hub-mcp.js +17 -0
  98. package/dist/mcp/context-hub-mcp.js.map +1 -1
  99. package/dist/types/flows.d.ts +69 -0
  100. package/dist/types/flows.d.ts.map +1 -0
  101. package/dist/types/flows.js +10 -0
  102. package/dist/types/flows.js.map +1 -0
  103. package/dist/types/map.d.ts +10 -1
  104. package/dist/types/map.d.ts.map +1 -1
  105. package/dist/types/map.js.map +1 -1
  106. package/dist/types/telemetry-digest.d.ts +73 -0
  107. package/dist/types/telemetry-digest.d.ts.map +1 -0
  108. package/dist/types/telemetry-digest.js +5 -0
  109. package/dist/types/telemetry-digest.js.map +1 -0
  110. package/dist/types/telemetry.d.ts +69 -0
  111. package/dist/types/telemetry.d.ts.map +1 -0
  112. package/dist/types/telemetry.js +5 -0
  113. package/dist/types/telemetry.js.map +1 -0
  114. package/dist/utils/jfl-paths.d.ts +1 -0
  115. package/dist/utils/jfl-paths.d.ts.map +1 -1
  116. package/dist/utils/jfl-paths.js +1 -0
  117. package/dist/utils/jfl-paths.js.map +1 -1
  118. package/dist/utils/settings-validator.d.ts +3 -2
  119. package/dist/utils/settings-validator.d.ts.map +1 -1
  120. package/dist/utils/settings-validator.js +25 -6
  121. package/dist/utils/settings-validator.js.map +1 -1
  122. package/package.json +3 -2
  123. package/scripts/session/fix-tracked-logs.sh +97 -0
  124. package/scripts/session/session-cleanup.sh +4 -1
  125. package/scripts/session/session-end.sh +10 -0
  126. package/scripts/session/session-init.sh +16 -0
  127. package/scripts/session/session-sync.sh +10 -0
  128. package/template/scripts/session/session-sync.sh +10 -0
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @purpose Manage narrowly-scoped agents — init scaffolding, list, status
3
+ */
4
+ export declare function agentCommand(action?: string, nameOrOptions?: string, options?: {
5
+ description?: string;
6
+ }): Promise<void>;
7
+ //# sourceMappingURL=agent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/commands/agent.ts"],"names":[],"mappings":"AAAA;;GAEG;AAsKH,wBAAsB,YAAY,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA6B7H"}
@@ -0,0 +1,170 @@
1
+ /**
2
+ * @purpose Manage narrowly-scoped agents — init scaffolding, list, status
3
+ */
4
+ import chalk from "chalk";
5
+ import * as fs from "fs";
6
+ import * as path from "path";
7
+ import { generateManifest, generatePolicy, generateLifecycle } from "../lib/agent-manifest.js";
8
+ function getAgentsDir() {
9
+ return path.join(process.cwd(), ".jfl", "agents");
10
+ }
11
+ function getFlowsDir() {
12
+ return path.join(process.cwd(), ".jfl", "flows");
13
+ }
14
+ async function initAgent(name, options = {}) {
15
+ if (!/^[a-z0-9-]+$/.test(name)) {
16
+ console.log(chalk.red(`\n Invalid agent name: "${name}"`));
17
+ console.log(chalk.gray(" Use lowercase letters, numbers, and hyphens only\n"));
18
+ return;
19
+ }
20
+ const agentDir = path.join(getAgentsDir(), name);
21
+ if (fs.existsSync(agentDir)) {
22
+ console.log(chalk.yellow(`\n Agent "${name}" already exists at .jfl/agents/${name}/\n`));
23
+ return;
24
+ }
25
+ fs.mkdirSync(agentDir, { recursive: true });
26
+ const manifestContent = generateManifest(name, options.description);
27
+ fs.writeFileSync(path.join(agentDir, "manifest.yaml"), manifestContent);
28
+ const policyContent = generatePolicy();
29
+ fs.writeFileSync(path.join(agentDir, "policy.json"), policyContent);
30
+ const triggerPattern = "session:ended";
31
+ const lifecycleContent = generateLifecycle(name, triggerPattern);
32
+ const flowsDir = getFlowsDir();
33
+ fs.mkdirSync(flowsDir, { recursive: true });
34
+ fs.writeFileSync(path.join(flowsDir, `${name}.yaml`), lifecycleContent);
35
+ console.log(chalk.bold(`\n Agent scaffolded: ${name}\n`));
36
+ console.log(chalk.gray(" Created:"));
37
+ console.log(` .jfl/agents/${name}/manifest.yaml`);
38
+ console.log(` .jfl/agents/${name}/policy.json`);
39
+ console.log(` .jfl/flows/${name}.yaml`);
40
+ console.log();
41
+ console.log(chalk.gray(" Next steps:"));
42
+ console.log(` 1. Edit manifest.yaml — set triggers, capabilities, runtime`);
43
+ console.log(` 2. Edit policy.json — set cost limits, approval gates`);
44
+ console.log(` 3. Edit .jfl/flows/${name}.yaml — customize lifecycle flow`);
45
+ console.log(` 4. Run ${chalk.cyan("jfl agent status " + name)} to verify`);
46
+ console.log();
47
+ }
48
+ async function listAgents() {
49
+ const agentsDir = getAgentsDir();
50
+ console.log(chalk.bold("\n Registered Agents\n"));
51
+ if (!fs.existsSync(agentsDir)) {
52
+ console.log(chalk.gray(" No agents registered."));
53
+ console.log(chalk.gray(` Run ${chalk.cyan("jfl agent init <name>")} to create one.\n`));
54
+ return;
55
+ }
56
+ const entries = fs.readdirSync(agentsDir).filter(f => fs.statSync(path.join(agentsDir, f)).isDirectory());
57
+ if (entries.length === 0) {
58
+ console.log(chalk.gray(" No agents registered."));
59
+ console.log(chalk.gray(` Run ${chalk.cyan("jfl agent init <name>")} to create one.\n`));
60
+ return;
61
+ }
62
+ for (const name of entries) {
63
+ const manifestPath = path.join(agentsDir, name, "manifest.yaml");
64
+ const policyPath = path.join(agentsDir, name, "policy.json");
65
+ const hasManifest = fs.existsSync(manifestPath);
66
+ const hasPolicy = fs.existsSync(policyPath);
67
+ let description = "";
68
+ let agentType = "";
69
+ if (hasManifest) {
70
+ try {
71
+ const { parse } = await import("yaml");
72
+ const manifest = parse(fs.readFileSync(manifestPath, "utf-8"));
73
+ description = manifest.description || "";
74
+ agentType = manifest.type || "";
75
+ }
76
+ catch { }
77
+ }
78
+ const status = hasManifest && hasPolicy ? chalk.green("ok") : chalk.yellow("incomplete");
79
+ const typeTag = agentType ? chalk.gray(` [${agentType}]`) : "";
80
+ const desc = description ? chalk.gray(` — ${description}`) : "";
81
+ console.log(` ${status} ${chalk.bold(name)}${typeTag}${desc}`);
82
+ }
83
+ console.log();
84
+ }
85
+ async function statusAgent(name) {
86
+ const agentDir = path.join(getAgentsDir(), name);
87
+ if (!fs.existsSync(agentDir)) {
88
+ console.log(chalk.red(`\n Agent "${name}" not found.`));
89
+ console.log(chalk.gray(` Run ${chalk.cyan("jfl agent init " + name)} to create it.\n`));
90
+ return;
91
+ }
92
+ console.log(chalk.bold(`\n Agent: ${name}\n`));
93
+ const manifestPath = path.join(agentDir, "manifest.yaml");
94
+ const policyPath = path.join(agentDir, "policy.json");
95
+ const flowPath = path.join(getFlowsDir(), `${name}.yaml`);
96
+ const checks = [
97
+ { file: "manifest.yaml", path: manifestPath },
98
+ { file: "policy.json", path: policyPath },
99
+ { file: `flows/${name}.yaml`, path: flowPath },
100
+ ];
101
+ for (const check of checks) {
102
+ if (fs.existsSync(check.path)) {
103
+ console.log(chalk.green(" [ok] ") + check.file);
104
+ }
105
+ else {
106
+ console.log(chalk.red(" [!!] ") + check.file + chalk.gray(" — missing"));
107
+ }
108
+ }
109
+ if (fs.existsSync(manifestPath)) {
110
+ try {
111
+ const { parse } = await import("yaml");
112
+ const manifest = parse(fs.readFileSync(manifestPath, "utf-8"));
113
+ console.log();
114
+ console.log(chalk.gray(" Type: ") + (manifest.type || "unknown"));
115
+ console.log(chalk.gray(" Version: ") + (manifest.version || "0.0.0"));
116
+ if (manifest.triggers?.length) {
117
+ const triggers = manifest.triggers.map((t) => t.pattern || t.schedule).join(", ");
118
+ console.log(chalk.gray(" Triggers: ") + triggers);
119
+ }
120
+ if (manifest.capabilities?.length) {
121
+ console.log(chalk.gray(" Capabilities: ") + manifest.capabilities.join(", "));
122
+ }
123
+ if (manifest.runtime?.command) {
124
+ console.log(chalk.gray(" Runtime: ") + manifest.runtime.command + " " + (manifest.runtime.args || []).join(" "));
125
+ }
126
+ }
127
+ catch { }
128
+ }
129
+ if (fs.existsSync(policyPath)) {
130
+ try {
131
+ const policy = JSON.parse(fs.readFileSync(policyPath, "utf-8"));
132
+ console.log(chalk.gray(" Cost limit: ") + `$${policy.cost_limit_usd}`);
133
+ console.log(chalk.gray(" Approval: ") + policy.approval_gate);
134
+ console.log(chalk.gray(" Max conc.: ") + policy.max_concurrent);
135
+ }
136
+ catch { }
137
+ }
138
+ console.log();
139
+ }
140
+ export async function agentCommand(action, nameOrOptions, options) {
141
+ switch (action) {
142
+ case "init":
143
+ if (!nameOrOptions) {
144
+ console.log(chalk.red("\n Agent name required."));
145
+ console.log(chalk.gray(" Usage: jfl agent init <name>\n"));
146
+ return;
147
+ }
148
+ await initAgent(nameOrOptions, options || {});
149
+ break;
150
+ case "list":
151
+ await listAgents();
152
+ break;
153
+ case "status":
154
+ if (!nameOrOptions) {
155
+ console.log(chalk.red("\n Agent name required."));
156
+ console.log(chalk.gray(" Usage: jfl agent status <name>\n"));
157
+ return;
158
+ }
159
+ await statusAgent(nameOrOptions);
160
+ break;
161
+ default:
162
+ console.log(chalk.bold("\n jfl agent — Manage narrowly-scoped agents\n"));
163
+ console.log(chalk.gray(" Commands:"));
164
+ console.log(" jfl agent init <name> Scaffold a new agent (manifest + policy + flows)");
165
+ console.log(" jfl agent list List registered agents");
166
+ console.log(" jfl agent status <name> Show agent health and config");
167
+ console.log();
168
+ }
169
+ }
170
+ //# sourceMappingURL=agent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent.js","sourceRoot":"","sources":["../../src/commands/agent.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,KAAK,EAAE,MAAM,IAAI,CAAA;AACxB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAA;AAC5B,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,iBAAiB,EAAiB,MAAM,0BAA0B,CAAA;AAE7G,SAAS,YAAY;IACnB,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAA;AACnD,CAAC;AAED,SAAS,WAAW;IAClB,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,CAAA;AAClD,CAAC;AAED,KAAK,UAAU,SAAS,CAAC,IAAY,EAAE,UAAoC,EAAE;IAC3E,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,4BAA4B,IAAI,GAAG,CAAC,CAAC,CAAA;QAC3D,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,sDAAsD,CAAC,CAAC,CAAA;QAC/E,OAAM;IACR,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE,IAAI,CAAC,CAAA;IAChD,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC5B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,cAAc,IAAI,mCAAmC,IAAI,KAAK,CAAC,CAAC,CAAA;QACzF,OAAM;IACR,CAAC;IAED,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAE3C,MAAM,eAAe,GAAG,gBAAgB,CAAC,IAAI,EAAE,OAAO,CAAC,WAAW,CAAC,CAAA;IACnE,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,eAAe,CAAC,EAAE,eAAe,CAAC,CAAA;IAEvE,MAAM,aAAa,GAAG,cAAc,EAAE,CAAA;IACtC,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,aAAa,CAAC,EAAE,aAAa,CAAC,CAAA;IAEnE,MAAM,cAAc,GAAG,eAAe,CAAA;IACtC,MAAM,gBAAgB,GAAG,iBAAiB,CAAC,IAAI,EAAE,cAAc,CAAC,CAAA;IAEhE,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAA;IAC9B,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAC3C,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,IAAI,OAAO,CAAC,EAAE,gBAAgB,CAAC,CAAA;IAEvE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,yBAAyB,IAAI,IAAI,CAAC,CAAC,CAAA;IAC1D,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAA;IACrC,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,gBAAgB,CAAC,CAAA;IACpD,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,cAAc,CAAC,CAAA;IAClD,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,OAAO,CAAC,CAAA;IAC1C,OAAO,CAAC,GAAG,EAAE,CAAA;IACb,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,CAAA;IACxC,OAAO,CAAC,GAAG,CAAC,iEAAiE,CAAC,CAAA;IAC9E,OAAO,CAAC,GAAG,CAAC,2DAA2D,CAAC,CAAA;IACxE,OAAO,CAAC,GAAG,CAAC,0BAA0B,IAAI,kCAAkC,CAAC,CAAA;IAC7E,OAAO,CAAC,GAAG,CAAC,cAAc,KAAK,CAAC,IAAI,CAAC,mBAAmB,GAAG,IAAI,CAAC,YAAY,CAAC,CAAA;IAC7E,OAAO,CAAC,GAAG,EAAE,CAAA;AACf,CAAC;AAED,KAAK,UAAU,UAAU;IACvB,MAAM,SAAS,GAAG,YAAY,EAAE,CAAA;IAEhC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC,CAAA;IAElD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC9B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC,CAAA;QAClD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,KAAK,CAAC,IAAI,CAAC,uBAAuB,CAAC,mBAAmB,CAAC,CAAC,CAAA;QACxF,OAAM;IACR,CAAC;IAED,MAAM,OAAO,GAAG,EAAE,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CACnD,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CACnD,CAAA;IAED,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC,CAAA;QAClD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,KAAK,CAAC,IAAI,CAAC,uBAAuB,CAAC,mBAAmB,CAAC,CAAC,CAAA;QACxF,OAAM;IACR,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;QAC3B,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,eAAe,CAAC,CAAA;QAChE,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,aAAa,CAAC,CAAA;QAE5D,MAAM,WAAW,GAAG,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,CAAA;QAC/C,MAAM,SAAS,GAAG,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,CAAA;QAE3C,IAAI,WAAW,GAAG,EAAE,CAAA;QACpB,IAAI,SAAS,GAAG,EAAE,CAAA;QAClB,IAAI,WAAW,EAAE,CAAC;YAChB,IAAI,CAAC;gBACH,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,CAAA;gBACtC,MAAM,QAAQ,GAAG,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,CAAA;gBAC9D,WAAW,GAAG,QAAQ,CAAC,WAAW,IAAI,EAAE,CAAA;gBACxC,SAAS,GAAG,QAAQ,CAAC,IAAI,IAAI,EAAE,CAAA;YACjC,CAAC;YAAC,MAAM,CAAC,CAAA,CAAC;QACZ,CAAC;QAED,MAAM,MAAM,GAAG,WAAW,IAAI,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,YAAY,CAAC,CAAA;QACxF,MAAM,OAAO,GAAG,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;QAC9D,MAAM,IAAI,GAAG,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;QAC/D,OAAO,CAAC,GAAG,CAAC,KAAK,MAAM,KAAK,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,OAAO,GAAG,IAAI,EAAE,CAAC,CAAA;IAClE,CAAC;IAED,OAAO,CAAC,GAAG,EAAE,CAAA;AACf,CAAC;AAED,KAAK,UAAU,WAAW,CAAC,IAAY;IACrC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE,IAAI,CAAC,CAAA;IAEhD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,cAAc,IAAI,cAAc,CAAC,CAAC,CAAA;QACxD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,KAAK,CAAC,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC,kBAAkB,CAAC,CAAC,CAAA;QACxF,OAAM;IACR,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,cAAc,IAAI,IAAI,CAAC,CAAC,CAAA;IAE/C,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,eAAe,CAAC,CAAA;IACzD,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAA;IACrD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,GAAG,IAAI,OAAO,CAAC,CAAA;IAEzD,MAAM,MAAM,GAAG;QACb,EAAE,IAAI,EAAE,eAAe,EAAE,IAAI,EAAE,YAAY,EAAE;QAC7C,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,UAAU,EAAE;QACzC,EAAE,IAAI,EAAE,SAAS,IAAI,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE;KAC/C,CAAA;IAED,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YAC9B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,CAAA;QAClD,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,GAAG,KAAK,CAAC,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAA;QAC3E,CAAC;IACH,CAAC;IAED,IAAI,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QAChC,IAAI,CAAC;YACH,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,CAAA;YACtC,MAAM,QAAQ,GAAG,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,CAAA;YAC9D,OAAO,CAAC,GAAG,EAAE,CAAA;YACb,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,IAAI,SAAS,CAAC,CAAC,CAAA;YAC1E,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,IAAI,OAAO,CAAC,CAAC,CAAA;YAC3E,IAAI,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC;gBAC9B,MAAM,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;gBACtF,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,kBAAkB,CAAC,GAAG,QAAQ,CAAC,CAAA;YACxD,CAAC;YACD,IAAI,QAAQ,CAAC,YAAY,EAAE,MAAM,EAAE,CAAC;gBAClC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,kBAAkB,CAAC,GAAG,QAAQ,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAA;YAChF,CAAC;YACD,IAAI,QAAQ,CAAC,OAAO,EAAE,OAAO,EAAE,CAAC;gBAC9B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,kBAAkB,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC,OAAO,GAAG,GAAG,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;YACxH,CAAC;QACH,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;IACZ,CAAC;IAED,IAAI,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC9B,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAAA;YAC/D,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,kBAAkB,CAAC,GAAG,IAAI,MAAM,CAAC,cAAc,EAAE,CAAC,CAAA;YACzE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,kBAAkB,CAAC,GAAG,MAAM,CAAC,aAAa,CAAC,CAAA;YAClE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,kBAAkB,CAAC,GAAG,MAAM,CAAC,cAAc,CAAC,CAAA;QACrE,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;IACZ,CAAC;IAED,OAAO,CAAC,GAAG,EAAE,CAAA;AACf,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,MAAe,EAAE,aAAsB,EAAE,OAAkC;IAC5G,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,MAAM;YACT,IAAI,CAAC,aAAa,EAAE,CAAC;gBACnB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC,CAAA;gBAClD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,kCAAkC,CAAC,CAAC,CAAA;gBAC3D,OAAM;YACR,CAAC;YACD,MAAM,SAAS,CAAC,aAAa,EAAE,OAAO,IAAI,EAAE,CAAC,CAAA;YAC7C,MAAK;QACP,KAAK,MAAM;YACT,MAAM,UAAU,EAAE,CAAA;YAClB,MAAK;QACP,KAAK,QAAQ;YACX,IAAI,CAAC,aAAa,EAAE,CAAC;gBACnB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC,CAAA;gBAClD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,oCAAoC,CAAC,CAAC,CAAA;gBAC7D,OAAM;YACR,CAAC;YACD,MAAM,WAAW,CAAC,aAAa,CAAC,CAAA;YAChC,MAAK;QACP;YACE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,iDAAiD,CAAC,CAAC,CAAA;YAC1E,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAA;YACtC,OAAO,CAAC,GAAG,CAAC,gFAAgF,CAAC,CAAA;YAC7F,OAAO,CAAC,GAAG,CAAC,sDAAsD,CAAC,CAAA;YACnE,OAAO,CAAC,GAAG,CAAC,4DAA4D,CAAC,CAAA;YACzE,OAAO,CAAC,GAAG,EAAE,CAAA;IACjB,CAAC;AACH,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"context-hub.d.ts","sourceRoot":"","sources":["../../src/commands/context-hub.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAy7BH,wBAAgB,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,CAiBjF;AA2ND,wBAAsB,qBAAqB,CAAC,IAAI,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAiHxF;AAMD,wBAAsB,iBAAiB,CACrC,MAAM,CAAC,EAAE,MAAM,EACf,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAO,iBAicnE"}
1
+ {"version":3,"file":"context-hub.d.ts","sourceRoot":"","sources":["../../src/commands/context-hub.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAoiCH,wBAAgB,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,CAiBjF;AA2ND,wBAAsB,qBAAqB,CAAC,IAAI,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAiHxF;AAMD,wBAAsB,iBAAiB,CACrC,MAAM,CAAC,EAAE,MAAM,EACf,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAO,iBAwenE"}
@@ -21,7 +21,10 @@ import { getProjectPort } from "../utils/context-hub-port.js";
21
21
  import { getConfigValue, setConfig } from "../utils/jfl-config.js";
22
22
  import Conf from "conf";
23
23
  import { MAPEventBus } from "../lib/map-event-bus.js";
24
+ import { FlowEngine } from "../lib/flow-engine.js";
24
25
  import { WebSocketServer } from "ws";
26
+ import { telemetry } from "../lib/telemetry.js";
27
+ import { transformHookPayload } from "../lib/hook-transformer.js";
25
28
  const PID_FILE = ".jfl/context-hub.pid";
26
29
  const LOG_FILE = ".jfl/logs/context-hub.log";
27
30
  const TOKEN_FILE = ".jfl/context-hub.token";
@@ -417,6 +420,32 @@ function getUnifiedContext(projectRoot, query, taskType) {
417
420
  // ============================================================================
418
421
  function createServer(projectRoot, port, eventBus) {
419
422
  const server = http.createServer((req, res) => {
423
+ const requestStart = Date.now();
424
+ const pathname = new URL(req.url || "/", `http://localhost:${port}`).pathname;
425
+ // Intercept writeHead to capture status code for telemetry
426
+ let capturedStatus = 200;
427
+ const originalWriteHead = res.writeHead.bind(res);
428
+ res.writeHead = function (statusCode, ...args) {
429
+ capturedStatus = statusCode;
430
+ return originalWriteHead(statusCode, ...args);
431
+ };
432
+ // Track request on response finish (skip health/OPTIONS/dashboard)
433
+ const shouldTrack = req.method !== "OPTIONS"
434
+ && pathname !== "/health"
435
+ && !pathname.startsWith("/dashboard");
436
+ if (shouldTrack) {
437
+ res.on('finish', () => {
438
+ telemetry.track({
439
+ category: 'context_hub',
440
+ event: 'context_hub:request',
441
+ endpoint: pathname,
442
+ method: req.method,
443
+ status_code: capturedStatus,
444
+ duration_ms: Date.now() - requestStart,
445
+ hub_port: port,
446
+ });
447
+ });
448
+ }
420
449
  // CORS - include Authorization header
421
450
  res.setHeader("Access-Control-Allow-Origin", "*");
422
451
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
@@ -446,8 +475,56 @@ function createServer(projectRoot, port, eventBus) {
446
475
  });
447
476
  return;
448
477
  }
478
+ // Hook ingestion (Claude Code HTTP hooks)
479
+ // No auth required — localhost-only. Always returns 200.
480
+ if (url.pathname === "/api/hooks" && req.method === "POST") {
481
+ const remoteAddr = req.socket.remoteAddress || "";
482
+ const isLocalhost = remoteAddr === "127.0.0.1" || remoteAddr === "::1" || remoteAddr === "::ffff:127.0.0.1";
483
+ if (!isLocalhost) {
484
+ res.writeHead(403, { "Content-Type": "application/json" });
485
+ res.end(JSON.stringify({ error: "localhost only" }));
486
+ return;
487
+ }
488
+ let body = "";
489
+ let bodySize = 0;
490
+ const MAX_BODY = 64 * 1024;
491
+ req.on("data", (chunk) => {
492
+ bodySize += typeof chunk === "string" ? chunk.length : chunk.byteLength;
493
+ if (bodySize <= MAX_BODY) {
494
+ body += chunk;
495
+ }
496
+ });
497
+ req.on("end", () => {
498
+ try {
499
+ if (bodySize > MAX_BODY) {
500
+ console.warn(`[hooks] Dropped oversized payload: ${bodySize} bytes`);
501
+ res.writeHead(200, { "Content-Type": "application/json" });
502
+ res.end(JSON.stringify({ ok: true, dropped: true }));
503
+ return;
504
+ }
505
+ const payload = JSON.parse(body);
506
+ const partial = transformHookPayload(payload);
507
+ if (eventBus) {
508
+ eventBus.emit(partial);
509
+ }
510
+ res.writeHead(200, { "Content-Type": "application/json" });
511
+ res.end(JSON.stringify({ ok: true }));
512
+ }
513
+ catch (err) {
514
+ console.warn(`[hooks] Parse error: ${err.message}`);
515
+ res.writeHead(200, { "Content-Type": "application/json" });
516
+ res.end(JSON.stringify({ ok: true, parse_error: true }));
517
+ }
518
+ });
519
+ return;
520
+ }
449
521
  // All other endpoints require auth
450
522
  if (!validateAuth(req, projectRoot, url)) {
523
+ telemetry.track({
524
+ category: 'context_hub',
525
+ event: 'context_hub:auth_failed',
526
+ endpoint: pathname,
527
+ });
451
528
  res.writeHead(401, { "Content-Type": "application/json" });
452
529
  res.end(JSON.stringify({
453
530
  error: "Unauthorized",
@@ -479,6 +556,15 @@ function createServer(projectRoot, port, eventBus) {
479
556
  if (maxItems && context.items.length > maxItems) {
480
557
  context.items = context.items.slice(0, maxItems);
481
558
  }
559
+ telemetry.track({
560
+ category: 'context_hub',
561
+ event: 'context_hub:context_loaded',
562
+ item_count: context.items.length,
563
+ journal_count: context.items.filter(i => i.source === 'journal').length,
564
+ knowledge_count: context.items.filter(i => i.source === 'knowledge').length,
565
+ code_count: context.items.filter(i => i.source === 'code').length,
566
+ query_length: query ? query.length : 0,
567
+ });
482
568
  res.writeHead(200, { "Content-Type": "application/json" });
483
569
  res.end(JSON.stringify(context));
484
570
  }
@@ -501,10 +587,19 @@ function createServer(projectRoot, port, eventBus) {
501
587
  res.end(JSON.stringify({ error: "query required" }));
502
588
  return;
503
589
  }
590
+ const searchStart = Date.now();
504
591
  const context = getUnifiedContext(projectRoot, query);
505
592
  context.items = context.items
506
593
  .filter(item => item.relevance && item.relevance > 0)
507
594
  .slice(0, maxItems);
595
+ telemetry.track({
596
+ category: 'context_hub',
597
+ event: 'context_hub:search',
598
+ result_count: context.items.length,
599
+ duration_ms: Date.now() - searchStart,
600
+ has_query: true,
601
+ query_length: query.length,
602
+ });
508
603
  res.writeHead(200, { "Content-Type": "application/json" });
509
604
  res.end(JSON.stringify(context));
510
605
  }
@@ -1319,7 +1414,14 @@ export async function contextHubCommand(action, options = {}) {
1319
1414
  process.on("uncaughtException", (err) => {
1320
1415
  console.error(`Uncaught exception: ${err.message}`);
1321
1416
  console.error(err.stack);
1322
- // Don't exit - log and continue
1417
+ telemetry.track({
1418
+ category: 'error',
1419
+ event: 'error:hub_crash',
1420
+ error_type: err.constructor.name,
1421
+ error_code: err.code || undefined,
1422
+ hub_port: port,
1423
+ hub_uptime_s: isListening ? Math.floor((Date.now() - hubStartTime) / 1000) : 0,
1424
+ });
1323
1425
  });
1324
1426
  process.on("unhandledRejection", (reason, promise) => {
1325
1427
  console.error(`Unhandled rejection at ${promise}: ${reason}`);
@@ -1327,17 +1429,30 @@ export async function contextHubCommand(action, options = {}) {
1327
1429
  });
1328
1430
  server.on("error", (err) => {
1329
1431
  console.error(`Server error: ${err.message}`);
1432
+ telemetry.track({
1433
+ category: 'error',
1434
+ event: 'error:hub_server',
1435
+ error_type: err.constructor.name,
1436
+ error_code: err.code || undefined,
1437
+ hub_port: port,
1438
+ });
1330
1439
  if (err.code === "EADDRINUSE") {
1331
1440
  console.error(`Port ${port} is already in use. Exiting.`);
1332
1441
  process.exit(1);
1333
1442
  }
1334
- // For other errors, don't exit
1335
1443
  });
1444
+ const hubStartTime = Date.now();
1336
1445
  server.listen(port, async () => {
1337
1446
  isListening = true;
1338
1447
  const timestamp = new Date().toISOString();
1339
1448
  console.log(`[${timestamp}] Context Hub listening on port ${port}`);
1340
1449
  console.log(`[${timestamp}] PID: ${process.pid}`);
1450
+ telemetry.track({
1451
+ category: 'context_hub',
1452
+ event: 'context_hub:started',
1453
+ hub_port: port,
1454
+ duration_ms: Date.now() - hubStartTime,
1455
+ });
1341
1456
  // Initialize memory system
1342
1457
  try {
1343
1458
  await initializeDatabase();
@@ -1355,6 +1470,17 @@ export async function contextHubCommand(action, options = {}) {
1355
1470
  console.error(`[${timestamp}] Failed to initialize memory system:`, err.message);
1356
1471
  // Don't exit - memory is optional
1357
1472
  }
1473
+ // Start flow engine
1474
+ try {
1475
+ const flowEngine = new FlowEngine(eventBus, projectRoot);
1476
+ const flowCount = await flowEngine.start();
1477
+ if (flowCount > 0) {
1478
+ console.log(`[${timestamp}] Flow engine started with ${flowCount} active flow(s)`);
1479
+ }
1480
+ }
1481
+ catch (err) {
1482
+ console.error(`[${timestamp}] Failed to start flow engine:`, err.message);
1483
+ }
1358
1484
  console.log(`[${timestamp}] MAP event bus initialized (buffer: 1000, subscribers: ${eventBus.getSubscriberCount()})`);
1359
1485
  console.log(`[${timestamp}] Ready to serve requests`);
1360
1486
  });
@@ -1378,6 +1504,12 @@ export async function contextHubCommand(action, options = {}) {
1378
1504
  console.log(`[${timestamp}] Received ${signal}`);
1379
1505
  console.log(`[${timestamp}] PID: ${process.pid}, Parent PID: ${process.ppid}`);
1380
1506
  console.log(`[${timestamp}] Shutting down...`);
1507
+ telemetry.track({
1508
+ category: 'context_hub',
1509
+ event: 'context_hub:stopped',
1510
+ hub_port: port,
1511
+ hub_uptime_s: Math.floor((Date.now() - hubStartTime) / 1000),
1512
+ });
1381
1513
  server.close(() => {
1382
1514
  eventBus.destroy();
1383
1515
  console.log(`[${new Date().toISOString()}] Server closed`);