@naisys/hub 3.0.0-beta.16 → 3.0.0-beta.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.env.example ADDED
@@ -0,0 +1,5 @@
1
+ # Hub server port
2
+ HUB_PORT=3101
3
+
4
+ # NAISYS folder for logs and auto-generated TLS certificates
5
+ NAISYS_FOLDER="~/.naisys"
@@ -29,6 +29,13 @@ export async function createHubConfigService(naisysServer, { hubDb }, logService
29
29
  }
30
30
  logService.log(`[Hub:Config] Seeded ${entries.length} variables from .env file into database`);
31
31
  }
32
+ // Ensure well-known variables exist so they show up in supervisor UI
33
+ await ensureVariables(hubDb, [
34
+ { key: "GOOGLE_SEARCH_ENGINE_ID" },
35
+ { key: "SPEND_LIMIT_DOLLARS" },
36
+ { key: "SPEND_LIMIT_HOURS" },
37
+ { key: "TARGET_VERSION" },
38
+ ]);
32
39
  /** Read variables from DB and build a ConfigResponse */
33
40
  async function buildConfigPayload() {
34
41
  const rows = await hubDb.variables.findMany();
@@ -82,4 +89,22 @@ export async function createHubConfigService(naisysServer, { hubDb }, logService
82
89
  getConfig: () => cachedConfig,
83
90
  };
84
91
  }
92
+ /** Create variable placeholders if they don't already exist */
93
+ export async function ensureVariables(hubDb, keys) {
94
+ for (const { key, sensitive } of keys) {
95
+ const existing = await hubDb.variables.findUnique({ where: { key } });
96
+ if (!existing) {
97
+ await hubDb.variables.create({
98
+ data: {
99
+ key,
100
+ value: "",
101
+ sensitive: sensitive ?? false,
102
+ export_to_shell: false,
103
+ created_by: "hub",
104
+ updated_by: "hub",
105
+ },
106
+ });
107
+ }
108
+ }
109
+ }
85
110
  //# sourceMappingURL=hubConfigService.js.map
@@ -1,14 +1,22 @@
1
1
  import { HubEvents } from "@naisys/hub-protocol";
2
+ import { getHubVersion } from "../version.js";
2
3
  /** Pushes the host list to all connections when connected hosts change */
3
4
  export function createHubHostService(naisysServer, hostRegistrar, logService) {
4
5
  let cachedHostListJson = "";
5
6
  function broadcastHostList(newConnection) {
6
- const connectedHostIds = new Set(naisysServer.getConnectedClients().map((c) => c.getHostId()));
7
- const hosts = hostRegistrar.getAllHosts().map((h) => ({
8
- ...h,
9
- online: connectedHostIds.has(h.hostId),
10
- }));
11
- const payload = { hosts };
7
+ // Index connected clients by hostId for O(1) lookup of online + version
8
+ const clientByHostId = new Map(naisysServer
9
+ .getConnectedClients()
10
+ .map((c) => [c.getHostId(), c]));
11
+ const hosts = hostRegistrar.getAllHosts().map((h) => {
12
+ const client = clientByHostId.get(h.hostId);
13
+ return {
14
+ ...h,
15
+ online: !!client,
16
+ version: client?.getClientVersion() || "",
17
+ };
18
+ });
19
+ const payload = { hubVersion: getHubVersion(), hosts };
12
20
  const json = JSON.stringify(payload);
13
21
  // Send to the newly connecting client directly
14
22
  if (newConnection) {
@@ -1,6 +1,7 @@
1
1
  import { builtInImageModels, builtInLlmModels, dbFieldsToImageModel, dbFieldsToLlmModel, imageModelToDbFields, llmModelToDbFields, } from "@naisys/common";
2
2
  import { loadCustomModels } from "@naisys/common-node";
3
3
  import { HubEvents } from "@naisys/hub-protocol";
4
+ import { ensureVariables } from "./hubConfigService.js";
4
5
  /** Hub handler that seeds models on startup, pushes them on connect, and broadcasts on change */
5
6
  export async function createHubModelsService(naisysServer, { hubDb }, logService) {
6
7
  // Seed models table from built-in + YAML custom models (one-time, skips if non-empty)
@@ -102,5 +103,13 @@ async function seedModels(hubDb, logService) {
102
103
  else {
103
104
  logService.log(`[Hub:Models] Models already seeded`);
104
105
  }
106
+ // Ensure API key variables referenced by built-in models exist in the variables table
107
+ // so they show up in the supervisor UI for the user to configure
108
+ const apiKeyVars = [
109
+ ...new Set([...builtInLlmModels, ...builtInImageModels]
110
+ .map((m) => m.apiKeyVar)
111
+ .filter(Boolean)),
112
+ ];
113
+ await ensureVariables(hubDb, apiKeyVars.map((key) => ({ key, sensitive: true })));
105
114
  }
106
115
  //# sourceMappingURL=hubModelsService.js.map
package/dist/naisysHub.js CHANGED
@@ -1,4 +1,4 @@
1
- import { expandNaisysFolder } from "@naisys/common-node";
1
+ import { ensureDotEnv, expandNaisysFolder } from "@naisys/common-node";
2
2
  import { createHubDatabaseService } from "@naisys/hub-database";
3
3
  import { program } from "commander";
4
4
  import dotenv from "dotenv";
@@ -112,6 +112,7 @@ export const startHub = async (startupType, startSupervisor, plugins, startupAge
112
112
  // Start server if this file is run directly
113
113
  if (process.argv[1] === fileURLToPath(import.meta.url)) {
114
114
  dotenv.config({ quiet: true });
115
+ await ensureDotEnv(new URL("../.env.example", import.meta.url));
115
116
  expandNaisysFolder();
116
117
  program
117
118
  .argument("[agent-path]", "Path to agent configuration file to seed the database (optional)")
@@ -1,4 +1,4 @@
1
- import { toUrlSafeKey } from "@naisys/common";
1
+ import { adminAgentConfig, toUrlSafeKey } from "@naisys/common";
2
2
  import { loadAgentConfigs } from "@naisys/common-node";
3
3
  import { randomBytes, randomUUID } from "crypto";
4
4
  /** Seeds agent configs from YAML files into an empty database. Skips if users already exist. */
@@ -10,9 +10,22 @@ export async function seedAgentConfigs({ hubDb }, logService, startupAgentPath)
10
10
  logService.log("[Hub:AgentRegistrar] Agents already seeded");
11
11
  return;
12
12
  }
13
- // Default to CWD when no path specified (matches standalone hub behavior)
14
- const users = loadAgentConfigs(startupAgentPath || "");
15
- await seedUsersToDatabase(hubDb, logService, users);
13
+ if (startupAgentPath) {
14
+ const users = loadAgentConfigs(startupAgentPath);
15
+ await seedUsersToDatabase(hubDb, logService, users);
16
+ }
17
+ else {
18
+ // No seed path: just create the admin agent
19
+ const adminUsers = new Map();
20
+ adminUsers.set(1, {
21
+ userId: 1,
22
+ username: adminAgentConfig.username,
23
+ enabled: true,
24
+ leadUserId: undefined,
25
+ config: adminAgentConfig,
26
+ });
27
+ await seedUsersToDatabase(hubDb, logService, adminUsers);
28
+ }
16
29
  }
17
30
  async function seedUsersToDatabase(hubDb, logService, users) {
18
31
  // First pass: create all users, build loader userId → DB id map
@@ -3,7 +3,7 @@
3
3
  * Each connected NAISYS instance gets its own NaisysConnection instance.
4
4
  */
5
5
  export function createNaisysConnection(socket, connectionInfo, raiseEvent, logService) {
6
- const { hostId, hostName, connectedAt, hostType } = connectionInfo;
6
+ const { hostId, hostName, connectedAt, hostType, clientVersion } = connectionInfo;
7
7
  logService.log(`[Hub:Connection] NAISYS instance connected: ${hostName} (${hostId})`);
8
8
  // Forward all socket events to hub's emit function
9
9
  // Note: Socket.IO passes (eventName, ...args) where last arg may be an ack callback
@@ -40,6 +40,7 @@ export function createNaisysConnection(socket, connectionInfo, raiseEvent, logSe
40
40
  getConnectedAt: () => connectedAt,
41
41
  getSocketId: () => socket.id,
42
42
  getHostType: () => hostType,
43
+ getClientVersion: () => clientVersion,
43
44
  };
44
45
  }
45
46
  //# sourceMappingURL=naisysConnection.js.map
@@ -59,7 +59,7 @@ export function createNaisysServer(nsp, initialHubAccessKey, logService, hostReg
59
59
  }
60
60
  // Authentication middleware
61
61
  nsp.use(async (socket, next) => {
62
- const { hubAccessKey: clientAccessKey, hostName, hostType: rawHostType, } = socket.handshake.auth;
62
+ const { hubAccessKey: clientAccessKey, hostName, hostType: rawHostType, clientVersion, } = socket.handshake.auth;
63
63
  if (!clientAccessKey || clientAccessKey !== hubAccessKey) {
64
64
  logService.log(`[Hub] Connection rejected: invalid access key from ${socket.handshake.address}`);
65
65
  return next(new Error("Invalid access key"));
@@ -79,6 +79,8 @@ export function createNaisysServer(nsp, initialHubAccessKey, logService, hostReg
79
79
  socket.data.hostId = hostId;
80
80
  socket.data.hostName = hostName;
81
81
  socket.data.hostType = hostType;
82
+ socket.data.clientVersion =
83
+ typeof clientVersion === "string" ? clientVersion : "";
82
84
  next();
83
85
  }
84
86
  catch (err) {
@@ -88,13 +90,14 @@ export function createNaisysServer(nsp, initialHubAccessKey, logService, hostReg
88
90
  });
89
91
  // Handle new connections
90
92
  nsp.on("connection", (socket) => {
91
- const { hostId, hostName, hostType } = socket.data;
93
+ const { hostId, hostName, hostType, clientVersion } = socket.data;
92
94
  // Create connection handler for this socket, passing our emit function
93
95
  const connection = createNaisysConnection(socket, {
94
96
  hostId,
95
97
  hostName,
96
98
  connectedAt: new Date(),
97
99
  hostType,
100
+ clientVersion,
98
101
  }, raiseEvent, logService);
99
102
  if (hostType === "supervisor") {
100
103
  supervisorConnections.push(connection);
@@ -0,0 +1,12 @@
1
+ import { getGitCommitHash } from "@naisys/common-node";
2
+ import { readFileSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ const __dirname = dirname(fileURLToPath(import.meta.url));
6
+ // Read the hub's own package.json (one level up from dist/)
7
+ const pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
8
+ const commitHash = getGitCommitHash(__dirname);
9
+ export function getHubVersion() {
10
+ return commitHash ? `${pkg.version}/${commitHash}` : pkg.version;
11
+ }
12
+ //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naisys/hub",
3
- "version": "3.0.0-beta.16",
3
+ "version": "3.0.0-beta.17",
4
4
  "description": "NAISYS Hub - Adds persistence and multi-instance coordination to NAISYS",
5
5
  "type": "module",
6
6
  "main": "dist/naisysHub.js",
@@ -13,7 +13,7 @@
13
13
  "./services/runnerServer": "./dist/services/runnerServer.js"
14
14
  },
15
15
  "scripts": {
16
- "clean": "rimraf dist",
16
+ "clean": "npx rimraf dist",
17
17
  "dev": "tsx watch src/naisysHub.ts",
18
18
  "start": "node dist/naisysHub.js",
19
19
  "build": "tsc",
@@ -24,12 +24,13 @@
24
24
  "files": [
25
25
  "dist",
26
26
  "bin",
27
+ ".env.example",
27
28
  "!dist/**/*.map",
28
29
  "!dist/**/*.d.ts",
29
30
  "!dist/**/*.d.ts.map"
30
31
  ],
31
32
  "peerDependencies": {
32
- "@naisys/supervisor": "3.0.0-beta.16"
33
+ "@naisys/supervisor": "3.0.0-beta.17"
33
34
  },
34
35
  "peerDependenciesMeta": {
35
36
  "@naisys/supervisor": {
@@ -37,10 +38,10 @@
37
38
  }
38
39
  },
39
40
  "dependencies": {
40
- "@naisys/common": "3.0.0-beta.16",
41
- "@naisys/common-node": "3.0.0-beta.16",
42
- "@naisys/hub-database": "3.0.0-beta.16",
43
- "@naisys/hub-protocol": "3.0.0-beta.16",
41
+ "@naisys/common": "3.0.0-beta.17",
42
+ "@naisys/common-node": "3.0.0-beta.17",
43
+ "@naisys/hub-database": "3.0.0-beta.17",
44
+ "@naisys/hub-protocol": "3.0.0-beta.17",
44
45
  "commander": "^14.0.3",
45
46
  "dotenv": "^17.3.1",
46
47
  "pino": "^10.3.1",