@ghostchain1/ghost-config 0.0.0-stage → 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 GhostChain
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,15 @@
1
- # Temporary Holding Version
1
+ # @ghostchain1/ghost-config
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ @ghostchain1/ghost-config
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @ghostchain1/ghost-config
9
+ ```
10
+
11
+ Part of the [GhostOS](https://github.com/ghostmode25/GhostOS) monorepo.
12
+
13
+ ## License
14
+
15
+ MIT
@@ -0,0 +1,3 @@
1
+ export declare function resolveGhostContractAddress(key: string, env?: Record<string, string | undefined>, legacyVariable?: string): string | undefined;
2
+ export declare function requireGhostContractAddress(key: string, env?: Record<string, string | undefined>, legacyVariable?: string): string;
3
+ //# sourceMappingURL=contracts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contracts.d.ts","sourceRoot":"","sources":["../src/contracts.ts"],"names":[],"mappings":"AAkBA,wBAAgB,2BAA2B,CACzC,GAAG,EAAE,MAAM,EACX,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,EACrD,cAAc,CAAC,EAAE,MAAM,GACtB,MAAM,GAAG,SAAS,CAepB;AAED,wBAAgB,2BAA2B,CACzC,GAAG,EAAE,MAAM,EACX,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,EACrD,cAAc,CAAC,EAAE,MAAM,GACtB,MAAM,CAQR"}
@@ -0,0 +1,37 @@
1
+ import { resolveGhostNetwork } from "./env.js";
2
+ const NETWORK_PREFIX = {
3
+ devnet: "DEVNET",
4
+ testnet: "TESTNET",
5
+ mainnet: "MAINNET",
6
+ };
7
+ const ADDRESS_PATTERN = /^0x[0-9a-fA-F]{40}$/;
8
+ function normalizeContractKey(key) {
9
+ const normalized = key.replaceAll("-", "_").toUpperCase();
10
+ if (!/^[A-Z0-9]+(?:_[A-Z0-9]+)*$/.test(normalized)) {
11
+ throw new Error(`Invalid Ghost contract key "${key}".`);
12
+ }
13
+ return normalized;
14
+ }
15
+ export function resolveGhostContractAddress(key, env = process.env, legacyVariable) {
16
+ const network = resolveGhostNetwork(env);
17
+ const contractKey = normalizeContractKey(key);
18
+ const scopedVariable = `${NETWORK_PREFIX[network]}_${contractKey}_ADDRESS`;
19
+ const value = env[scopedVariable] ??
20
+ (network === "devnet" && legacyVariable ? env[legacyVariable] : undefined);
21
+ if (value === undefined || value.trim() === "") {
22
+ return undefined;
23
+ }
24
+ if (!ADDRESS_PATTERN.test(value)) {
25
+ throw new Error(`${scopedVariable} must contain a 20-byte hexadecimal address.`);
26
+ }
27
+ return value;
28
+ }
29
+ export function requireGhostContractAddress(key, env = process.env, legacyVariable) {
30
+ const address = resolveGhostContractAddress(key, env, legacyVariable);
31
+ if (!address || /^0x0{40}$/i.test(address)) {
32
+ const network = resolveGhostNetwork(env);
33
+ const variable = `${NETWORK_PREFIX[network]}_${normalizeContractKey(key)}_ADDRESS`;
34
+ throw new Error(`${variable} must be set to a deployed contract address.`);
35
+ }
36
+ return address;
37
+ }
package/dist/env.d.ts ADDED
@@ -0,0 +1,35 @@
1
+ import { type GhostChainEndpoints, type GhostChainKey } from "@ghostchain1/ghost-chain-registry";
2
+ export declare const GHOST_NETWORKS: readonly ["devnet", "testnet", "mainnet"];
3
+ export type GhostNetwork = (typeof GHOST_NETWORKS)[number];
4
+ export type GhostRuntimeEndpoints = Record<Exclude<GhostChainKey, "ghostl3-litvybz">, GhostChainEndpoints> & Partial<Record<"ghostl3-litvybz", GhostChainEndpoints>>;
5
+ export interface GhostRuntimeConfig {
6
+ network: GhostNetwork;
7
+ chainId: number;
8
+ /**
9
+ * Chain IDs for the ACTIVE network — resolved from the nine-network matrix
10
+ * (plus the LitVybz app-chain identity). Not the mainnet GHOST_CHAIN_IDS
11
+ * constants: devnet/testnet/mainnet each have their own L1/L2/L3 IDs.
12
+ */
13
+ chainIds: {
14
+ ghostchain: number;
15
+ ghostl2: number;
16
+ ghostl3: number;
17
+ "ghostl3-litvybz": number;
18
+ };
19
+ endpoints: GhostRuntimeEndpoints;
20
+ rpcUrl: string;
21
+ wsUrl: string;
22
+ explorerUrl?: string;
23
+ apiUrl?: string;
24
+ gatewayUrl?: string;
25
+ authUrl?: string;
26
+ postgresHost: string;
27
+ postgresPort: number;
28
+ redisUrl: string;
29
+ }
30
+ export declare function resolveGhostNetwork(env?: Record<string, string | undefined>): GhostNetwork;
31
+ export declare function createGhostRuntimeConfig(env?: Record<string, string | undefined>): GhostRuntimeConfig;
32
+ export declare function requireGhostChainEndpoints(config: Pick<GhostRuntimeConfig, "endpoints" | "network">, key: GhostChainKey): GhostChainEndpoints;
33
+ export declare function logActiveGhostNetwork(network: GhostNetwork, log?: (message: string) => void): void;
34
+ export declare const env: GhostRuntimeConfig;
35
+ //# sourceMappingURL=env.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../src/env.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,mBAAmB,EACxB,KAAK,aAAa,EACnB,MAAM,mCAAmC,CAAC;AAE3C,eAAO,MAAM,cAAc,2CAA4C,CAAC;AACxE,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAC3D,MAAM,MAAM,qBAAqB,GAAG,MAAM,CACxC,OAAO,CAAC,aAAa,EAAE,iBAAiB,CAAC,EACzC,mBAAmB,CACpB,GACC,OAAO,CAAC,MAAM,CAAC,iBAAiB,EAAE,mBAAmB,CAAC,CAAC,CAAC;AAE1D,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,YAAY,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,QAAQ,EAAE;QACR,UAAU,EAAE,MAAM,CAAC;QACnB,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,MAAM,CAAC;QAChB,iBAAiB,EAAE,MAAM,CAAC;KAC3B,CAAC;IACF,SAAS,EAAE,qBAAqB,CAAC;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAkBD,wBAAgB,mBAAmB,CACjC,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,GACpD,YAAY,CAgCd;AA2KD,wBAAgB,wBAAwB,CACtC,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,GACpD,kBAAkB,CAwDpB;AAED,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,IAAI,CAAC,kBAAkB,EAAE,WAAW,GAAG,SAAS,CAAC,EACzD,GAAG,EAAE,aAAa,GACjB,mBAAmB,CAUrB;AAED,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,YAAY,EACrB,GAAG,GAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAmB,GAC5C,IAAI,CAEN;AAED,eAAO,MAAM,GAAG,oBAA6B,CAAC"}
package/dist/env.js ADDED
@@ -0,0 +1,219 @@
1
+ import { GHOST_CHAIN_IDS, GHOST_CHAIN_KEYS, GHOST_TARGET_CHAIN_IDS, getChainByKey, } from "@ghostchain1/ghost-chain-registry";
2
+ export const GHOST_NETWORKS = ["devnet", "testnet", "mainnet"];
3
+ const NETWORK_PREFIX = {
4
+ devnet: "DEVNET",
5
+ testnet: "TESTNET",
6
+ mainnet: "MAINNET",
7
+ };
8
+ const CORE_CHAIN_KEYS = ["ghostchain", "ghostl2", "ghostl3"];
9
+ function hasValue(value) {
10
+ return value !== undefined && value.trim() !== "";
11
+ }
12
+ function firstValue(...values) {
13
+ return values.find(hasValue);
14
+ }
15
+ export function resolveGhostNetwork(env = process.env) {
16
+ const value = env.GHOST_NETWORK;
17
+ if (!hasValue(value)) {
18
+ const legacyEnvironment = firstValue(env.GHOST_ENV, env.ENVIRONMENT, env.NET_ENV);
19
+ if (env.NODE_ENV === "production" ||
20
+ legacyEnvironment === "testnet" ||
21
+ legacyEnvironment === "mainnet") {
22
+ throw new Error("GHOST_NETWORK must be explicitly set to devnet, testnet, or mainnet outside local devnet.");
23
+ }
24
+ return "devnet";
25
+ }
26
+ if (!GHOST_NETWORKS.includes(value)) {
27
+ throw new Error(`Invalid GHOST_NETWORK "${value}". Allowed values: ${GHOST_NETWORKS.join(", ")}.`);
28
+ }
29
+ const network = value;
30
+ for (const marker of [env.GHOST_ENV, env.ENVIRONMENT, env.NET_ENV]) {
31
+ if (hasValue(marker) &&
32
+ GHOST_NETWORKS.includes(marker) &&
33
+ marker !== network) {
34
+ throw new Error(`GHOST_NETWORK=${network} conflicts with environment marker ${marker}.`);
35
+ }
36
+ }
37
+ return network;
38
+ }
39
+ function parseUrl(value, variable, allowedProtocols, network) {
40
+ let parsed;
41
+ try {
42
+ parsed = new URL(value);
43
+ }
44
+ catch {
45
+ throw new Error(`${variable} must be a valid absolute URL.`);
46
+ }
47
+ if (!allowedProtocols.includes(parsed.protocol)) {
48
+ throw new Error(`${variable} must use ${allowedProtocols.join(" or ")}; received ${parsed.protocol}.`);
49
+ }
50
+ if (network === "mainnet" &&
51
+ [
52
+ "localhost",
53
+ "127.0.0.1",
54
+ "::1",
55
+ "[::1]",
56
+ "192.168.122.205",
57
+ "10.50.10.103",
58
+ "10.50.10.131",
59
+ "10.50.20.186",
60
+ "10.50.30.174",
61
+ ].includes(parsed.hostname)) {
62
+ throw new Error(`${variable} cannot point to a devnet or testnet endpoint on mainnet.`);
63
+ }
64
+ return value;
65
+ }
66
+ function resolveChainEndpoint(key, network, env) {
67
+ const chain = getChainByKey(key);
68
+ const prefix = NETWORK_PREFIX[network];
69
+ const chainName = key.replaceAll("-", "_").toUpperCase();
70
+ const rpcVariable = `${prefix}_${chainName}_RPC_URL`;
71
+ const wsVariable = `${prefix}_${chainName}_WS_URL`;
72
+ const explorerVariable = `${prefix}_${chainName}_EXPLORER_URL`;
73
+ const rpcUrl = firstValue(key === "ghostchain" ? env[`${prefix}_RPC_URL`] : undefined, env[rpcVariable], network === "devnet"
74
+ ? (env[chain.rpcEnvVar] ??
75
+ (key === "ghostl3-litvybz" ? env.L3_RPC_URL : undefined) ??
76
+ chain.defaultRpcUrl)
77
+ : undefined);
78
+ const wsUrl = firstValue(key === "ghostchain" ? env[`${prefix}_WS_URL`] : undefined, env[wsVariable], network === "devnet"
79
+ ? (env[chain.wsEnvVar] ??
80
+ (key === "ghostl3-litvybz" ? env.L3_WS_URL : undefined) ??
81
+ chain.defaultWsUrl)
82
+ : undefined);
83
+ const explorerUrl = firstValue(key === "ghostchain" ? env[`${prefix}_EXPLORER_URL`] : undefined, env[explorerVariable], network === "devnet"
84
+ ? (env[chain.explorerEnvVar] ??
85
+ (key === "ghostl3-litvybz" ? env.L3_EXPLORER_URL : undefined) ??
86
+ chain.defaultExplorerUrl)
87
+ : undefined);
88
+ if (key === "ghostl3-litvybz" && !hasValue(rpcUrl) && !hasValue(wsUrl)) {
89
+ return undefined;
90
+ }
91
+ if (!hasValue(rpcUrl)) {
92
+ throw new Error(`Missing ${rpcVariable}${key === "ghostchain" ? ` or ${prefix}_RPC_URL` : ""} for GHOST_NETWORK=${network}.`);
93
+ }
94
+ if (!hasValue(wsUrl)) {
95
+ throw new Error(`Missing ${wsVariable}${key === "ghostchain" ? ` or ${prefix}_WS_URL` : ""} for GHOST_NETWORK=${network}.`);
96
+ }
97
+ const endpoints = {
98
+ rpcUrl: parseUrl(rpcUrl, rpcVariable, ["http:", "https:"], network),
99
+ wsUrl: parseUrl(wsUrl, wsVariable, ["ws:", "wss:"], network),
100
+ };
101
+ if (hasValue(explorerUrl)) {
102
+ endpoints.explorerUrl = parseUrl(explorerUrl, explorerVariable, ["http:", "https:"], network);
103
+ }
104
+ return endpoints;
105
+ }
106
+ function resolveNetworkChainId(network, env) {
107
+ const variable = `${NETWORK_PREFIX[network]}_CHAIN_ID`;
108
+ // Canonical L1 identity for this environment (nine-network matrix).
109
+ const expected = GHOST_TARGET_CHAIN_IDS[network].ghostchain;
110
+ const value = env[variable];
111
+ if (!hasValue(value)) {
112
+ if (network !== "devnet") {
113
+ throw new Error(`${variable} is required for GHOST_NETWORK=${network}.`);
114
+ }
115
+ return expected;
116
+ }
117
+ const chainId = Number(value);
118
+ if (!Number.isSafeInteger(chainId) || chainId !== expected) {
119
+ throw new Error(`${variable} must match the registered GhostChain L1 chain ID for ${network} (${expected}); received "${value}".`);
120
+ }
121
+ return chainId;
122
+ }
123
+ /**
124
+ * Per-network chain IDs for the active network — resolved from the
125
+ * nine-network matrix with `GHOST_{ENV}_{LAYER}_CHAIN_ID` overrides for
126
+ * in-flight re-provisions. `L1_CHAIN_ID` / `{PREFIX}_CHAIN_ID` remains the
127
+ * canonical L1 override (validated by resolveNetworkChainId).
128
+ */
129
+ function resolveChainIdsForNetwork(network, env) {
130
+ const override = (suffix) => {
131
+ const raw = env[`GHOST_${network.toUpperCase()}_${suffix}`];
132
+ if (!hasValue(raw))
133
+ return undefined;
134
+ const parsed = Number(raw);
135
+ return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : undefined;
136
+ };
137
+ return {
138
+ ...GHOST_CHAIN_IDS,
139
+ ghostchain: resolveNetworkChainId(network, env),
140
+ ghostl2: override("L2_CHAIN_ID") ?? GHOST_TARGET_CHAIN_IDS[network].ghostl2,
141
+ ghostl3: override("L3_CHAIN_ID") ?? GHOST_TARGET_CHAIN_IDS[network].ghostl3,
142
+ };
143
+ }
144
+ function assertMainnetConfiguration(env) {
145
+ const unsafeName = /(?:PRIVATE_KEY|MNEMONIC|SEED|SIGNING_KEY|FAUCET|MOCK|TEST_TOKEN|DATABASE|DEBUG|API_KEY|SECRET|CREDENTIAL|CONTRACT|ADDRESS)/i;
146
+ const unsafeSetting = Object.entries(env).find(([name, value]) => {
147
+ if (!hasValue(value)) {
148
+ return false;
149
+ }
150
+ return ((/^(?:DEVNET|TESTNET)_/i.test(name) && unsafeName.test(name)) ||
151
+ (/_(?:DEVNET|TESTNET)$/i.test(name) && unsafeName.test(name)));
152
+ });
153
+ if (unsafeSetting) {
154
+ throw new Error(`${unsafeSetting[0]} is a devnet/testnet-only setting and cannot be present when GHOST_NETWORK=mainnet.`);
155
+ }
156
+ }
157
+ export function createGhostRuntimeConfig(env = process.env) {
158
+ const network = resolveGhostNetwork(env);
159
+ if (network === "mainnet" && env.GHOST_PRODUCTION !== "true") {
160
+ throw new Error("GHOST_PRODUCTION=true is required when GHOST_NETWORK=mainnet.");
161
+ }
162
+ if (network === "mainnet") {
163
+ assertMainnetConfiguration(env);
164
+ }
165
+ const endpoints = {};
166
+ for (const key of GHOST_CHAIN_KEYS) {
167
+ const endpoint = resolveChainEndpoint(key, network, env);
168
+ if (endpoint) {
169
+ endpoints[key] = endpoint;
170
+ }
171
+ }
172
+ for (const key of CORE_CHAIN_KEYS) {
173
+ if (!endpoints[key]) {
174
+ throw new Error(`Missing ${network} RPC configuration for ${key}.`);
175
+ }
176
+ }
177
+ const prefix = NETWORK_PREFIX[network];
178
+ const chainIds = resolveChainIdsForNetwork(network, env);
179
+ const chainId = chainIds.ghostchain;
180
+ const l1Endpoints = endpoints.ghostchain;
181
+ const apiUrl = env[`${prefix}_API_URL`] ??
182
+ (network === "devnet" ? (env.API_URL ?? env.NEXT_PUBLIC_API_URL) : undefined);
183
+ const gatewayUrl = env[`${prefix}_GATEWAY_URL`] ?? (network === "devnet" ? env.GATEWAY_URL : undefined);
184
+ const authUrl = env[`${prefix}_AUTH_URL`] ?? (network === "devnet" ? env.NEXT_PUBLIC_AUTH_URL : undefined);
185
+ return {
186
+ network,
187
+ chainId,
188
+ chainIds,
189
+ endpoints,
190
+ rpcUrl: l1Endpoints.rpcUrl,
191
+ wsUrl: l1Endpoints.wsUrl,
192
+ explorerUrl: l1Endpoints.explorerUrl,
193
+ apiUrl: hasValue(apiUrl)
194
+ ? parseUrl(apiUrl, `${prefix}_API_URL`, ["http:", "https:"], network)
195
+ : undefined,
196
+ gatewayUrl: hasValue(gatewayUrl)
197
+ ? parseUrl(gatewayUrl, `${prefix}_GATEWAY_URL`, ["http:", "https:"], network)
198
+ : undefined,
199
+ authUrl: hasValue(authUrl)
200
+ ? parseUrl(authUrl, `${prefix}_AUTH_URL`, ["http:", "https:"], network)
201
+ : undefined,
202
+ postgresHost: env.POSTGRES_HOST ?? "127.0.0.1",
203
+ postgresPort: Number(env.POSTGRES_PORT ?? "5432"),
204
+ redisUrl: env.REDIS_URL ?? "redis://127.0.0.1:6379",
205
+ };
206
+ }
207
+ export function requireGhostChainEndpoints(config, key) {
208
+ const endpoints = config.endpoints[key];
209
+ if (!endpoints) {
210
+ const prefix = NETWORK_PREFIX[config.network];
211
+ const chainName = key.replaceAll("-", "_").toUpperCase();
212
+ throw new Error(`${prefix}_${chainName}_RPC_URL and ${prefix}_${chainName}_WS_URL are required to use ${key} on ${config.network}.`);
213
+ }
214
+ return endpoints;
215
+ }
216
+ export function logActiveGhostNetwork(network, log = console.info) {
217
+ log(`Active GhostOS network: ${network}`);
218
+ }
219
+ export const env = createGhostRuntimeConfig();
@@ -0,0 +1,63 @@
1
+ import { type GhostNetwork } from "./env.js";
2
+ /**
3
+ * GhostOS namespaced configuration (`GHOSTOS_*`).
4
+ *
5
+ * This module is a compatibility adapter, not a second configuration system:
6
+ * every `GHOSTOS_*` key falls back to the pre-existing variable that already
7
+ * owns the setting (for example `GHOST_NETWORK`, `GHOST_AI_LLM_URL`,
8
+ * `GHOST_ROOT`) so current deployments keep working unchanged. When both are
9
+ * set, the `GHOSTOS_*` name wins and invalid values are surfaced instead of
10
+ * silently ignored.
11
+ *
12
+ * Secrets are never resolved here; secret material is referenced by file path
13
+ * (systemd `LoadCredential=`) or injected by Vault at runtime.
14
+ */
15
+ export declare const GHOSTOS_ENVIRONMENTS: readonly ["development", "test", "staging", "production"];
16
+ export type GhostOSEnvironment = (typeof GHOSTOS_ENVIRONMENTS)[number];
17
+ /** Autonomy levels understood by the GhostOS assistant (`ghostos/assistant`). */
18
+ export type GhostAiAutonomy = "L0" | "L1" | "L2";
19
+ /**
20
+ * Map a `GHOSTOS_AI_AUTONOMY_MODE` value onto the assistant's `L0|L1|L2`.
21
+ * Unknown values throw: silently defaulting an autonomy level would be a
22
+ * safety bug.
23
+ */
24
+ export declare function toGhostAiAutonomy(value: string): GhostAiAutonomy;
25
+ export interface GhostOSConfig {
26
+ env: GhostOSEnvironment;
27
+ /** Repository / installation root (`GHOSTOS_ROOT`, legacy `GHOST_ROOT`). */
28
+ root?: string;
29
+ configDir: string;
30
+ dataDir: string;
31
+ logDir: string;
32
+ api: {
33
+ host: string;
34
+ port: number;
35
+ };
36
+ ai: {
37
+ enabled: boolean;
38
+ provider: "local" | "gateway";
39
+ model?: string;
40
+ /** OpenAI-compatible endpoint (`GHOSTOS_AI_ENDPOINT`, legacy `GHOST_AI_LLM_URL`). */
41
+ endpoint?: string;
42
+ autonomy: GhostAiAutonomy;
43
+ };
44
+ serviceDiscoveryEnabled: boolean;
45
+ healthCheckEnabled: boolean;
46
+ metricsEnabled: boolean;
47
+ /** Optional event bus / NATS URL (`GHOSTOS_EVENT_BUS_URL`). */
48
+ eventBusUrl?: string;
49
+ chain: {
50
+ /** Reuses the existing chain-routing resolver (`GHOSTOS_CHAIN_ENV`, legacy `GHOST_NETWORK`). */
51
+ env: GhostNetwork;
52
+ rpcUrl?: string;
53
+ chainId?: number;
54
+ };
55
+ }
56
+ export declare function resolveGhostOSConfig(env?: Record<string, string | undefined>): GhostOSConfig;
57
+ /**
58
+ * Variables a GhostOS host must provide before core services start, when the
59
+ * corresponding feature is enabled. Used by deployment preflight checks; this
60
+ * list is additive and does not change service-level validation.
61
+ */
62
+ export declare function requiredGhostOSVariables(config: GhostOSConfig): string[];
63
+ //# sourceMappingURL=ghostos.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ghostos.d.ts","sourceRoot":"","sources":["../src/ghostos.ts"],"names":[],"mappings":"AACA,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,UAAU,CAAC;AAElE;;;;;;;;;;;;GAYG;AAEH,eAAO,MAAM,oBAAoB,2DAA4D,CAAC;AAC9F,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEvE,iFAAiF;AACjF,MAAM,MAAM,eAAe,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAcjD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,eAAe,CAQhE;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,kBAAkB,CAAC;IACxB,4EAA4E;IAC5E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACpC,EAAE,EAAE;QACF,OAAO,EAAE,OAAO,CAAC;QACjB,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;QAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,qFAAqF;QACrF,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,eAAe,CAAC;KAC3B,CAAC;IACF,uBAAuB,EAAE,OAAO,CAAC;IACjC,kBAAkB,EAAE,OAAO,CAAC;IAC5B,cAAc,EAAE,OAAO,CAAC;IACxB,+DAA+D;IAC/D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE;QACL,gGAAgG;QAChG,GAAG,EAAE,YAAY,CAAC;QAClB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;CACH;AAMD,wBAAgB,oBAAoB,CAClC,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,GACpD,aAAa,CAqFf;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,EAAE,CAYxE"}
@@ -0,0 +1,163 @@
1
+ import { environmentOfChainId } from "@ghostchain1/ghost-chain-registry";
2
+ import { resolveGhostNetwork } from "./env.js";
3
+ /**
4
+ * GhostOS namespaced configuration (`GHOSTOS_*`).
5
+ *
6
+ * This module is a compatibility adapter, not a second configuration system:
7
+ * every `GHOSTOS_*` key falls back to the pre-existing variable that already
8
+ * owns the setting (for example `GHOST_NETWORK`, `GHOST_AI_LLM_URL`,
9
+ * `GHOST_ROOT`) so current deployments keep working unchanged. When both are
10
+ * set, the `GHOSTOS_*` name wins and invalid values are surfaced instead of
11
+ * silently ignored.
12
+ *
13
+ * Secrets are never resolved here; secret material is referenced by file path
14
+ * (systemd `LoadCredential=`) or injected by Vault at runtime.
15
+ */
16
+ export const GHOSTOS_ENVIRONMENTS = ["development", "test", "staging", "production"];
17
+ const AUTONOMY_ALIASES = {
18
+ l0: "L0",
19
+ l1: "L1",
20
+ l2: "L2",
21
+ // Directive-specified friendly aliases mapped onto the assistant's levels.
22
+ suggest: "L0",
23
+ observe: "L0",
24
+ approval_required: "L0",
25
+ develop: "L1",
26
+ merge: "L2",
27
+ };
28
+ /**
29
+ * Map a `GHOSTOS_AI_AUTONOMY_MODE` value onto the assistant's `L0|L1|L2`.
30
+ * Unknown values throw: silently defaulting an autonomy level would be a
31
+ * safety bug.
32
+ */
33
+ export function toGhostAiAutonomy(value) {
34
+ const mapped = AUTONOMY_ALIASES[value.trim().toLowerCase()];
35
+ if (!mapped) {
36
+ throw new Error(`Unknown autonomy level "${value}". Allowed: ${Object.keys(AUTONOMY_ALIASES).join(", ")}.`);
37
+ }
38
+ return mapped;
39
+ }
40
+ function hasValue(value) {
41
+ return value !== undefined && value.trim() !== "";
42
+ }
43
+ export function resolveGhostOSConfig(env = process.env) {
44
+ // The existing resolver already validates GHOST_NETWORK and its legacy
45
+ // markers, and requires explicit network selection outside local devnet.
46
+ const chainEnv = resolveGhostNetwork({
47
+ ...env,
48
+ GHOST_NETWORK: firstValue(env.GHOSTOS_CHAIN_ENV, env.GHOST_NETWORK) ?? env.GHOST_NETWORK,
49
+ });
50
+ const aiProvider = firstValue(env.GHOSTOS_AI_PROVIDER, "local");
51
+ if (aiProvider !== "local" && aiProvider !== "gateway") {
52
+ throw new Error(`GHOSTOS_AI_PROVIDER must be local or gateway; received "${aiProvider}".`);
53
+ }
54
+ const autonomyValue = firstValue(env.GHOSTOS_AI_AUTONOMY_MODE, "approval_required");
55
+ const rpcUrl = firstValue(env.GHOSTOS_RPC_URL, env.RPC_URL);
56
+ const chainIdRaw = firstValue(env.GHOSTOS_CHAIN_ID, env.CHAIN_ID);
57
+ let chainId;
58
+ if (hasValue(chainIdRaw)) {
59
+ chainId = Number(chainIdRaw);
60
+ if (!Number.isSafeInteger(chainId) || chainId <= 0) {
61
+ throw new Error(`GHOSTOS_CHAIN_ID must be a positive integer; received "${chainIdRaw}".`);
62
+ }
63
+ // Nine-network enforcement: the configured chain ID must be one of the
64
+ // nine active GhostOS identities AND must belong to the configured chain
65
+ // environment. Obsolete legacy IDs (901/903) and cross-environment pairings
66
+ // (e.g. mainnet chain ID on devnet) are rejected here.
67
+ const idEnvironment = environmentOfChainId(chainId);
68
+ if (!idEnvironment) {
69
+ throw new Error(`GHOSTOS_CHAIN_ID=${chainId} is not a registered GhostOS chain ID (nine-network matrix); ` +
70
+ `legacy fixed IDs 901/903 are obsolete.`);
71
+ }
72
+ if (idEnvironment !== chainEnv) {
73
+ throw new Error(`GHOSTOS_CHAIN_ID=${chainId} belongs to ${idEnvironment} but GHOSTOS_CHAIN_ENV=${chainEnv}; ` +
74
+ `cross-environment chain IDs are forbidden.`);
75
+ }
76
+ }
77
+ const config = {
78
+ env: parseEnvironment(env),
79
+ configDir: firstValue(env.GHOSTOS_CONFIG_DIR, "/etc/ghostos"),
80
+ dataDir: firstValue(env.GHOSTOS_DATA_DIR, "/var/lib/ghostos"),
81
+ logDir: firstValue(env.GHOSTOS_LOG_DIR, "/var/log/ghostos"),
82
+ api: {
83
+ host: firstValue(env.GHOSTOS_API_HOST, "127.0.0.1"),
84
+ port: hasValue(env.GHOSTOS_API_PORT)
85
+ ? parsePort("GHOSTOS_API_PORT", env.GHOSTOS_API_PORT)
86
+ : 8080,
87
+ },
88
+ ai: {
89
+ enabled: hasValue(env.GHOSTOS_AI_ENABLED)
90
+ ? parseBoolean("GHOSTOS_AI_ENABLED", env.GHOSTOS_AI_ENABLED)
91
+ : true,
92
+ provider: aiProvider,
93
+ endpoint: firstValue(env.GHOSTOS_AI_ENDPOINT, env.GHOST_AI_LLM_URL),
94
+ autonomy: toGhostAiAutonomy(autonomyValue),
95
+ },
96
+ serviceDiscoveryEnabled: hasValue(env.GHOSTOS_SERVICE_DISCOVERY_ENABLED)
97
+ ? parseBoolean("GHOSTOS_SERVICE_DISCOVERY_ENABLED", env.GHOSTOS_SERVICE_DISCOVERY_ENABLED)
98
+ : true,
99
+ healthCheckEnabled: hasValue(env.GHOSTOS_HEALTH_CHECK_ENABLED)
100
+ ? parseBoolean("GHOSTOS_HEALTH_CHECK_ENABLED", env.GHOSTOS_HEALTH_CHECK_ENABLED)
101
+ : true,
102
+ metricsEnabled: hasValue(env.GHOSTOS_METRICS_ENABLED)
103
+ ? parseBoolean("GHOSTOS_METRICS_ENABLED", env.GHOSTOS_METRICS_ENABLED)
104
+ : true,
105
+ eventBusUrl: firstValue(env.GHOSTOS_EVENT_BUS_URL, env.NATS_URL),
106
+ chain: {
107
+ env: chainEnv,
108
+ rpcUrl,
109
+ chainId,
110
+ },
111
+ };
112
+ const root = firstValue(env.GHOSTOS_ROOT, env.GHOST_ROOT);
113
+ if (hasValue(root))
114
+ config.root = root;
115
+ const model = firstValue(env.GHOSTOS_AI_MODEL);
116
+ if (hasValue(model))
117
+ config.ai.model = model;
118
+ return config;
119
+ }
120
+ /**
121
+ * Variables a GhostOS host must provide before core services start, when the
122
+ * corresponding feature is enabled. Used by deployment preflight checks; this
123
+ * list is additive and does not change service-level validation.
124
+ */
125
+ export function requiredGhostOSVariables(config) {
126
+ const required = [];
127
+ if (config.chain.env !== "devnet" && !hasValue(config.chain.rpcUrl)) {
128
+ required.push("GHOSTOS_RPC_URL");
129
+ }
130
+ if (config.chain.env !== "devnet" && config.chain.chainId === undefined) {
131
+ required.push("GHOSTOS_CHAIN_ID");
132
+ }
133
+ if (config.ai.provider === "gateway" && !hasValue(config.ai.endpoint)) {
134
+ required.push("GHOSTOS_AI_ENDPOINT");
135
+ }
136
+ return required;
137
+ }
138
+ function firstValue(...values) {
139
+ return values.find(hasValue);
140
+ }
141
+ function parseBoolean(name, value) {
142
+ if (value === "true" || value === "1")
143
+ return true;
144
+ if (value === "false" || value === "0")
145
+ return false;
146
+ throw new Error(`${name} must be true, false, 1 or 0; received "${value}".`);
147
+ }
148
+ function parsePort(name, value) {
149
+ const port = Number(value);
150
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
151
+ throw new Error(`${name} must be an integer 1-65535; received "${value}".`);
152
+ }
153
+ return port;
154
+ }
155
+ function parseEnvironment(env) {
156
+ const value = firstValue(env.GHOSTOS_ENV, env.NODE_ENV, "development");
157
+ if (value === "default")
158
+ return "development"; // bare `node` runs
159
+ if (!GHOSTOS_ENVIRONMENTS.includes(value)) {
160
+ throw new Error(`Invalid environment "${value}". Allowed: ${GHOSTOS_ENVIRONMENTS.join(", ")}.`);
161
+ }
162
+ return value;
163
+ }