naisys
Version:
NAISYS - Autonomous AI agent runner with built-in context management and cost tracking
129 lines • 5.03 kB
JavaScript
import { buildClientConfig } from "@naisys/common";
import { ConfigResponseSchema, HubEvents } from "@naisys/hub-protocol";
import dotenv from "dotenv";
import fs from "fs";
import { readFile } from "fs/promises";
import os from "os";
import path from "path";
import * as pathService from "./services/runtime/pathService.js";
export function createGlobalConfig(hubClient, supervisorUrl) {
let cachedConfig;
let configReadyPromise;
let configChangedHandler;
init();
function init() {
if (hubClient) {
let resolveConfig;
let rejectConfig;
configReadyPromise = new Promise((resolve, reject) => {
resolveConfig = resolve;
rejectConfig = reject;
});
hubClient.registerEvent(HubEvents.VARIABLES_UPDATED, async (data) => {
try {
const response = ConfigResponseSchema.parse(data);
if (!response.success || !response.config) {
rejectConfig(new Error(response.error || "Failed to get config from hub"));
return;
}
cachedConfig = await appendClientConfig(response.config);
resolveConfig();
configChangedHandler?.();
}
catch (error) {
rejectConfig(error instanceof Error ? error : new Error(String(error)));
}
});
}
else {
const { parsed: dotenvVars } = dotenv.config({ quiet: true });
const clientConfig = buildClientConfig(dotenvVars ?? {});
configReadyPromise = appendClientConfig(clientConfig).then((config) => {
cachedConfig = config;
});
}
}
async function appendClientConfig(clientConfig) {
/** Identifies this runner - shows after @ in prompt, used for multi-machine host identification */
const hostname = process.env.NAISYS_HOSTNAME || os.hostname();
const packageVersion = await getVersion();
return {
...clientConfig,
hostname,
packageVersion,
supervisorUrl,
};
}
async function getVersion() {
const installPath = pathService.getInstallPath();
const packageJsonPath = path.join(installPath, "package.json");
const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8"));
return packageJson.version;
}
/** Patch process.env and the cached config so runtime changes are visible. */
function patchRuntimeVariable(key, value, options = {}) {
process.env[key] = value;
// Patch cached config so runtime credential refreshes are visible
// immediately without waiting for a process restart.
if (cachedConfig) {
cachedConfig.variableMap[key] = value;
const exportToShell = options.exportToShell ??
(!hubClient || key in cachedConfig.shellVariableMap);
if (exportToShell) {
cachedConfig.shellVariableMap[key] = value;
}
else {
delete cachedConfig.shellVariableMap[key];
}
if (key === "NAISYS_HOSTNAME") {
cachedConfig.hostname = value;
}
}
}
/**
* Update a single key in the .env file, preserving comments and ordering.
* If the key exists, its value is replaced in-place.
* If the key does not exist, it is appended to the end of the file.
* Also updates process.env so the change is visible immediately.
*/
function setVariableValue(key, value, options = {}) {
const dotenvPath = path.resolve(".env");
const content = fs.existsSync(dotenvPath)
? fs.readFileSync(dotenvPath, "utf-8")
: "";
const lines = content.split("\n");
let found = false;
for (let i = 0; i < lines.length; i++) {
const trimmed = lines[i].trim();
if (!trimmed || trimmed.startsWith("#"))
continue;
const eqIdx = trimmed.indexOf("=");
if (eqIdx === -1)
continue;
const lineKey = trimmed.substring(0, eqIdx).trim();
if (lineKey === key) {
lines[i] = `${key}=${value}`;
found = true;
break;
}
}
if (!found) {
lines.push(`${key}=${value}`);
}
fs.writeFileSync(dotenvPath, lines.join("\n"));
patchRuntimeVariable(key, value, options);
}
function updateEnvValue(key, value) {
setVariableValue(key, value);
}
return {
globalConfig: () => cachedConfig,
waitForConfig: () => configReadyPromise,
onConfigChanged: (handler) => {
configChangedHandler = handler;
},
setVariableValue,
updateEnvValue,
};
}
//# sourceMappingURL=globalConfig.js.map