First Release of Claw3D (#11)
Co-authored-by: iamlukethedev <iamlukethedev@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
import { resolveStateDir } from "@/lib/clawdbot/paths";
|
||||
import {
|
||||
defaultStudioSettings,
|
||||
mergeStudioSettings,
|
||||
normalizeStudioSettings,
|
||||
type StudioSettings,
|
||||
type StudioSettingsPatch,
|
||||
} from "@/lib/studio/settings";
|
||||
|
||||
// Studio settings are intentionally stored as a local JSON file for a single-user workflow.
|
||||
// That includes gateway connection details, so treat the state directory as plaintext secret
|
||||
// storage and document any changes to this threat model in README.md and SECURITY.md.
|
||||
const SETTINGS_DIRNAME = "claw3d";
|
||||
const SETTINGS_FILENAME = "settings.json";
|
||||
const OPENCLAW_CONFIG_FILENAME = "openclaw.json";
|
||||
|
||||
export const resolveStudioSettingsPath = () =>
|
||||
path.join(resolveStateDir(), SETTINGS_DIRNAME, SETTINGS_FILENAME);
|
||||
|
||||
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
||||
Boolean(value && typeof value === "object");
|
||||
|
||||
const readOpenclawGatewayDefaults = (): { url: string; token: string } | null => {
|
||||
try {
|
||||
const configPath = path.join(resolveStateDir(), OPENCLAW_CONFIG_FILENAME);
|
||||
if (!fs.existsSync(configPath)) return null;
|
||||
const raw = fs.readFileSync(configPath, "utf8");
|
||||
const parsed = JSON.parse(raw) as unknown;
|
||||
if (!isRecord(parsed)) return null;
|
||||
const gateway = isRecord(parsed.gateway) ? parsed.gateway : null;
|
||||
if (!gateway) return null;
|
||||
const auth = isRecord(gateway.auth) ? gateway.auth : null;
|
||||
const token = typeof auth?.token === "string" ? auth.token.trim() : "";
|
||||
const port = typeof gateway.port === "number" && Number.isFinite(gateway.port) ? gateway.port : null;
|
||||
if (!token) return null;
|
||||
const url = port ? `ws://localhost:${port}` : "";
|
||||
if (!url) return null;
|
||||
return { url, token };
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const loadLocalGatewayDefaults = () => {
|
||||
return readOpenclawGatewayDefaults();
|
||||
};
|
||||
|
||||
export const loadStudioSettings = (): StudioSettings => {
|
||||
const settingsPath = resolveStudioSettingsPath();
|
||||
if (!fs.existsSync(settingsPath)) {
|
||||
const defaults = defaultStudioSettings();
|
||||
const gateway = loadLocalGatewayDefaults();
|
||||
return gateway ? { ...defaults, gateway } : defaults;
|
||||
}
|
||||
const raw = fs.readFileSync(settingsPath, "utf8");
|
||||
const parsed = JSON.parse(raw) as unknown;
|
||||
const settings = normalizeStudioSettings(parsed);
|
||||
if (!settings.gateway?.token) {
|
||||
const gateway = loadLocalGatewayDefaults();
|
||||
if (gateway) {
|
||||
return {
|
||||
...settings,
|
||||
gateway: settings.gateway?.url?.trim()
|
||||
? { url: settings.gateway.url.trim(), token: gateway.token }
|
||||
: gateway,
|
||||
};
|
||||
}
|
||||
}
|
||||
return settings;
|
||||
};
|
||||
|
||||
export const saveStudioSettings = (next: StudioSettings) => {
|
||||
const settingsPath = resolveStudioSettingsPath();
|
||||
const dir = path.dirname(settingsPath);
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
fs.writeFileSync(settingsPath, JSON.stringify(next, null, 2), "utf8");
|
||||
};
|
||||
|
||||
export const applyStudioSettingsPatch = (patch: StudioSettingsPatch): StudioSettings => {
|
||||
const current = loadStudioSettings();
|
||||
const next = mergeStudioSettings(current, patch);
|
||||
saveStudioSettings(next);
|
||||
return next;
|
||||
};
|
||||
Reference in New Issue
Block a user