@kheleshikhi/studio-agent 0.6.1

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.
@@ -0,0 +1,66 @@
1
+ import { CliError } from "./types.js";
2
+ export function observationSettings(input) {
3
+ const mode = input.mode || "semantic";
4
+ const durationMs = Math.round((input.duration ?? 8) * 1000);
5
+ const fps = input.fps ?? 4;
6
+ if (mode !== "semantic" && mode !== "fixed")
7
+ throw new CliError("Capture mode must be semantic or fixed.", "STUDIO_CAPTURE_SETTINGS_INVALID", 2);
8
+ if (!Number.isFinite(durationMs) || durationMs < 500 || durationMs > 10_000 ||
9
+ !Number.isInteger(fps) || fps < 1 || fps > 8 ||
10
+ input.width < 320 || input.width > 1280 || input.height < 240 || input.height > 960 ||
11
+ (mode === "fixed" && Math.ceil(durationMs / 1000 * fps) + 1 > 24)) {
12
+ throw new CliError("Capture is limited to 0.5–10 s, 1–8 fps, 320–1280 × 240–960, and 24 frames.", "STUDIO_CAPTURE_SETTINGS_INVALID", 2);
13
+ }
14
+ return { mode, durationMs, fps, width: input.width, height: input.height };
15
+ }
16
+ function object(value) {
17
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
18
+ }
19
+ function serial(value) { return JSON.stringify(value ?? null); }
20
+ /** Selects bounded checkpoints from the real live runtime, never simulated state. */
21
+ export class ObservationSampler {
22
+ settings;
23
+ checkpoints = [];
24
+ previous;
25
+ lastMotionAt = 0;
26
+ constructor(settings) {
27
+ this.settings = settings;
28
+ }
29
+ consider(atMs, observation, final = false) {
30
+ const previous = this.previous;
31
+ const last = this.checkpoints.at(-1);
32
+ const stateChanged = previous && serial(previous.active_states) !== serial(observation.active_states);
33
+ const variablesChanged = previous && serial(previous.host_variables) !== serial(observation.host_variables);
34
+ const interactionsChanged = previous && serial(previous.interactions) !== serial(observation.interactions);
35
+ const dispatchChanged = previous && serial(previous.machine_dispatches) !== serial(observation.machine_dispatches);
36
+ const geometryChanged = previous && previous.geometry_revision !== observation.geometry_revision;
37
+ const semantic = !!(stateChanged || variablesChanged || interactionsChanged || dispatchChanged);
38
+ const fixedDue = !last || atMs - last.at_ms >= 1000 / this.settings.fps - 20;
39
+ const motionDue = geometryChanged && atMs - this.lastMotionAt >= 1000;
40
+ const selected = !last || final || (this.settings.mode === "fixed" ? fixedDue : semantic || motionDue);
41
+ this.previous = observation;
42
+ if (final && last?.at_ms === atMs) {
43
+ last.label = "END";
44
+ last.observation = observation;
45
+ return undefined;
46
+ }
47
+ if (!selected || this.checkpoints.length >= (final ? 24 : 23))
48
+ return undefined;
49
+ if (motionDue)
50
+ this.lastMotionAt = atMs;
51
+ const label = !last ? "START" : final ? "END" : interactionsChanged ? "INTERACTION" : stateChanged ? "STATE CHANGE" : dispatchChanged ? "EVENT" : variablesChanged ? "VARIABLE CHANGE" : motionDue ? "MOTION" : "SAMPLE";
52
+ const checkpoint = { id: this.checkpoints.length + 1, at_ms: atMs, label, observation };
53
+ this.checkpoints.push(checkpoint);
54
+ return checkpoint;
55
+ }
56
+ }
57
+ export function assertCurrentObservation(observation, sourceHash, revision) {
58
+ if (observation.status !== "complete" || observation.preview_is_current !== true ||
59
+ observation.source_hash !== sourceHash || observation.source_revision !== revision) {
60
+ throw new CliError("The live preview changed or became stale during capture; no current-source evidence was produced.", "STUDIO_CAPTURE_STALE", 3, {
61
+ expected_source_hash: sourceHash, observed_source_hash: observation.source_hash,
62
+ expected_revision: revision, observed_revision: observation.source_revision,
63
+ });
64
+ }
65
+ }
66
+ //# sourceMappingURL=observation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"observation.js","sourceRoot":"","sources":["../src/observation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ export interface TraceIdentity {
3
+ attemptId?: string;
4
+ commandId?: string;
5
+ }
6
+ export declare const traceStorage: AsyncLocalStorage<TraceIdentity>;
7
+ export declare function setCliTrace(value: TraceIdentity): void;
8
+ export declare class AgentApi {
9
+ readonly origin: string;
10
+ private token?;
11
+ constructor(origin: string, token?: string | undefined);
12
+ setToken(value: string | undefined): void;
13
+ /**
14
+ * POST one file as multipart form data. The filename is always the neutral
15
+ * `upload.<extension>`; the caller's local path or name never leaves the machine.
16
+ */
17
+ upload<T>(route: string, field: string, extension: string, contentType: string, bytes: Uint8Array): Promise<T>;
18
+ request<T>(method: string, route: string, body?: unknown, authenticated?: boolean): Promise<T>;
19
+ }
20
+ export declare function daemonRpc<T>(path: string, options?: RequestInit): Promise<T>;
@@ -0,0 +1,99 @@
1
+ import { CliError } from "./types.js";
2
+ import { AsyncLocalStorage } from "node:async_hooks";
3
+ import { randomBytes } from "node:crypto";
4
+ export const traceStorage = new AsyncLocalStorage();
5
+ let cliTrace = {};
6
+ export function setCliTrace(value) { cliTrace = value; }
7
+ function traceHeaders(trace) {
8
+ return {
9
+ "X-Request-ID": `req_${randomBytes(12).toString("base64url")}`,
10
+ ...(trace.attemptId ? { "X-NerdBro-Attempt-ID": trace.attemptId } : {}),
11
+ ...(trace.commandId ? { "X-NerdBro-Command-ID": trace.commandId } : {}),
12
+ };
13
+ }
14
+ export class AgentApi {
15
+ origin;
16
+ token;
17
+ constructor(origin, token) {
18
+ this.origin = origin;
19
+ this.token = token;
20
+ }
21
+ setToken(value) {
22
+ this.token = value;
23
+ }
24
+ /**
25
+ * POST one file as multipart form data. The filename is always the neutral
26
+ * `upload.<extension>`; the caller's local path or name never leaves the machine.
27
+ */
28
+ async upload(route, field, extension, contentType, bytes) {
29
+ if (!/^[a-z0-9]{1,8}$/.test(extension))
30
+ throw new CliError("Upload extension is invalid.", "AGENT_UPLOAD_INVALID", 2);
31
+ const form = new FormData();
32
+ form.append(field, new Blob([new Uint8Array(bytes)], { type: contentType }), `upload.${extension}`);
33
+ return this.request("POST", route, form);
34
+ }
35
+ async request(method, route, body, authenticated = true) {
36
+ const headers = { Accept: "application/json", ...traceHeaders(traceStorage.getStore() || {}) };
37
+ const multipart = body instanceof FormData;
38
+ if (body !== undefined && !multipart)
39
+ headers["Content-Type"] = "application/json";
40
+ if (authenticated) {
41
+ if (!this.token)
42
+ throw new CliError("Run `nerdbro connect` first.", "AGENT_AUTH_REQUIRED", 4);
43
+ headers.Authorization = `Bearer ${this.token}`;
44
+ }
45
+ let response;
46
+ try {
47
+ response = await fetch(new URL(route, `${this.origin}/`), {
48
+ method,
49
+ headers,
50
+ body: body === undefined ? undefined : multipart ? body : JSON.stringify(body),
51
+ });
52
+ }
53
+ catch (error) {
54
+ throw new CliError(`Cannot reach ${this.origin}: ${String(error)}`, "AGENT_NETWORK_FAILURE");
55
+ }
56
+ const contentType = response.headers.get("content-type") || "";
57
+ if (!response.ok) {
58
+ const detail = contentType.includes("json") ? await response.json().catch(() => ({})) : await response.text();
59
+ const candidate = typeof detail === "object" && detail && "detail" in detail ? detail.detail : detail;
60
+ const text = typeof candidate === "string" ? candidate : JSON.stringify(candidate);
61
+ const code = /[A-Z][A-Z0-9_]{3,}/.exec(text)?.[0] || `HTTP_${response.status}`;
62
+ const exitCode = response.status === 409 ? 3 : response.status === 401 ? 4 : 1;
63
+ throw new CliError(text || response.statusText, code, exitCode, candidate);
64
+ }
65
+ if (!contentType.includes("json"))
66
+ return new Uint8Array(await response.arrayBuffer());
67
+ const payload = (await response.json());
68
+ return typeof payload === "object" && payload !== null && "data" in payload ? payload.data : payload;
69
+ }
70
+ }
71
+ export async function daemonRpc(path, options = {}) {
72
+ const { cleanStaleDaemonMetadata, readDaemonMetadata } = await import("./state.js");
73
+ await cleanStaleDaemonMetadata();
74
+ const metadata = await readDaemonMetadata();
75
+ if (!metadata)
76
+ throw new CliError("The local agent daemon is not running.", "AGENT_DAEMON_NOT_RUNNING", 4);
77
+ let response;
78
+ try {
79
+ response = await fetch(`http://127.0.0.1:${metadata.port}${path}`, {
80
+ ...options,
81
+ headers: {
82
+ "Content-Type": "application/json",
83
+ Authorization: `Bearer ${metadata.rpcSecret}`,
84
+ ...(cliTrace.attemptId ? { "X-NerdBro-Attempt-ID": cliTrace.attemptId } : {}),
85
+ ...(cliTrace.commandId ? { "X-NerdBro-Command-ID": cliTrace.commandId } : {}),
86
+ ...(options.headers || {}),
87
+ },
88
+ });
89
+ }
90
+ catch {
91
+ await cleanStaleDaemonMetadata();
92
+ throw new CliError("The local agent daemon did not respond.", "AGENT_DAEMON_UNAVAILABLE", 4);
93
+ }
94
+ const value = (await response.json());
95
+ if (!response.ok || value.ok === false)
96
+ throw new CliError(value.message || "Daemon request failed.", value.code, value.exitCode || 1, value.details);
97
+ return value;
98
+ }
99
+ //# sourceMappingURL=protocol.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ export type DriveStep = {
2
+ op: "set" | "fire" | "press" | "tap" | "drag" | "play" | "pause" | "seek" | "reset" | "wait" | "assert" | "capture";
3
+ target?: string;
4
+ value?: string | number | boolean;
5
+ path?: string;
6
+ equals?: string | number | boolean | null;
7
+ between?: [number, number];
8
+ timeoutMs?: number;
9
+ name?: string;
10
+ };
11
+ export type DriveScenario = {
12
+ version: 1;
13
+ steps: DriveStep[];
14
+ };
15
+ export declare function parseScenario(value: unknown): DriveScenario;
16
+ export declare function validObservationPath(path: string): boolean;
17
+ export declare function observedValue(observation: Record<string, unknown>, path: string): unknown;
18
+ export declare function matchesObservation(observation: Record<string, unknown>, step: DriveStep): boolean;
19
+ export declare function isDriveCommand(step: DriveStep): boolean;
@@ -0,0 +1,90 @@
1
+ import { CliError } from "./types.js";
2
+ const COMMANDS = new Set(["set", "fire", "press", "tap", "drag", "play", "pause", "seek", "reset"]);
3
+ const OPERATIONS = new Set([...COMMANDS, "wait", "assert", "capture"]);
4
+ const PATH_ROOTS = new Set([
5
+ "active_states", "host_variables", "motion_outputs", "learner_controls",
6
+ "recent_scene_event", "scene_event_sequence", "scene_clock_seconds",
7
+ "scene_clock_playing", "machine_dispatches", "math_graph", "runtime_ready",
8
+ "geometry_instance_ids", "error_summary",
9
+ "recent_audio_event", "audio_failed_count",
10
+ "captions", "visual_ready",
11
+ "gestures",
12
+ ]);
13
+ const IDENTIFIER = /^[A-Za-z][A-Za-z0-9_-]*$/;
14
+ function invalid(message) {
15
+ throw new CliError(message, "STUDIO_SCENARIO_INVALID", 2);
16
+ }
17
+ export function parseScenario(value) {
18
+ if (!value || typeof value !== "object" || Array.isArray(value))
19
+ invalid("Scenario must be an object.");
20
+ const document = value;
21
+ if (document.version !== 1 || !Array.isArray(document.steps) || !document.steps.length || document.steps.length > 64) {
22
+ invalid("Scenario requires version 1 and 1–64 steps.");
23
+ }
24
+ const steps = document.steps.map((raw, index) => {
25
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
26
+ invalid(`Step ${index} must be an object.`);
27
+ const step = raw;
28
+ if (typeof step.op !== "string" || !OPERATIONS.has(step.op))
29
+ invalid(`Step ${index} has an unsupported operation.`);
30
+ const allowed = new Set(["op", "target", "value", "path", "equals", "between", "timeoutMs", "name"]);
31
+ if (Object.keys(step).some((key) => !allowed.has(key)))
32
+ invalid(`Step ${index} has an unknown field.`);
33
+ if (["set", "fire", "press", "tap", "drag"].includes(step.op) &&
34
+ (typeof step.target !== "string" || !IDENTIFIER.test(step.target) || step.target.length > 80)) {
35
+ invalid(`Step ${index} needs a semantic target id.`);
36
+ }
37
+ if (["set", "drag", "seek"].includes(step.op) &&
38
+ (typeof step.value !== "number" && typeof step.value !== "boolean" && typeof step.value !== "string" ||
39
+ typeof step.value === "number" && !Number.isFinite(step.value))) {
40
+ invalid(`Step ${index} needs a finite value.`);
41
+ }
42
+ if (typeof step.value === "string" && step.value.length > 160)
43
+ invalid(`Step ${index} value is too long.`);
44
+ if (["wait", "assert"].includes(step.op)) {
45
+ if (typeof step.path !== "string" || !validObservationPath(step.path))
46
+ invalid(`Step ${index} needs an allowed observation path.`);
47
+ const hasEquals = Object.hasOwn(step, "equals");
48
+ const between = step.between;
49
+ if (hasEquals === (between !== undefined))
50
+ invalid(`Step ${index} needs exactly one comparison.`);
51
+ if (between !== undefined && (!Array.isArray(between) || between.length !== 2 ||
52
+ between.some((n) => typeof n !== "number" || !Number.isFinite(n)) || between[0] > between[1])) {
53
+ invalid(`Step ${index} has invalid bounds.`);
54
+ }
55
+ }
56
+ if (step.timeoutMs !== undefined && (typeof step.timeoutMs !== "number" || !Number.isInteger(step.timeoutMs) || step.timeoutMs < 1 || step.timeoutMs > 30_000)) {
57
+ invalid(`Step ${index} timeout must be 1–30000 ms.`);
58
+ }
59
+ if (step.name !== undefined && (typeof step.name !== "string" || !/^[A-Za-z0-9_-]{1,60}$/.test(step.name)))
60
+ invalid(`Step ${index} has invalid capture name.`);
61
+ return step;
62
+ });
63
+ return { version: 1, steps };
64
+ }
65
+ export function validObservationPath(path) {
66
+ const parts = path.split(".");
67
+ return parts.length <= 4 && PATH_ROOTS.has(parts[0]) &&
68
+ parts.slice(1).every((part) => /^[A-Za-z0-9_-]{1,80}$/.test(part));
69
+ }
70
+ export function observedValue(observation, path) {
71
+ if (!validObservationPath(path))
72
+ invalid("Observation path is not allowed.");
73
+ const parts = path.split(".");
74
+ if (parts[0] === "motion_outputs") {
75
+ const outputs = observation.motion_outputs;
76
+ if (outputs && typeof outputs === "object" && Object.hasOwn(outputs, parts.slice(1).join("."))) {
77
+ return outputs[parts.slice(1).join(".")];
78
+ }
79
+ }
80
+ return parts.reduce((value, key) => value && typeof value === "object" && Object.hasOwn(value, key)
81
+ ? value[key] : undefined, observation);
82
+ }
83
+ export function matchesObservation(observation, step) {
84
+ const value = observedValue(observation, step.path || "");
85
+ if (step.between)
86
+ return typeof value === "number" && value >= step.between[0] && value <= step.between[1];
87
+ return value === step.equals;
88
+ }
89
+ export function isDriveCommand(step) { return COMMANDS.has(step.op); }
90
+ //# sourceMappingURL=scenario.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scenario.js","sourceRoot":"","sources":["../src/scenario.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { AgentConfig, DaemonMetadata } from "./types.js";
2
+ export declare const DEFAULT_API_ORIGIN = "https://api.nerdbro.org";
3
+ export declare function stateDirectory(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): string;
4
+ export declare function cacheDirectory(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): string;
5
+ export declare function writeDaemonMetadata(value: DaemonMetadata): Promise<void>;
6
+ export declare function readDaemonMetadata(): Promise<DaemonMetadata | null>;
7
+ export declare function removeDaemonMetadata(): Promise<void>;
8
+ export declare function processExists(pid: number): boolean;
9
+ export declare function cleanStaleDaemonMetadata(): Promise<void>;
10
+ export declare function validateOrigin(raw: string): string;
11
+ export declare function readConfig(): Promise<AgentConfig>;
12
+ export declare function writeConfig(config: AgentConfig): Promise<void>;
13
+ export declare function isExecutable(file: string): Promise<boolean>;
package/dist/state.js ADDED
@@ -0,0 +1,111 @@
1
+ import { constants } from "node:fs";
2
+ import { access, mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { CliError } from "./types.js";
6
+ export const DEFAULT_API_ORIGIN = "https://api.nerdbro.org";
7
+ export function stateDirectory(env = process.env, platform = process.platform) {
8
+ if (env.NERDBRO_AGENT_STATE_DIR)
9
+ return path.resolve(env.NERDBRO_AGENT_STATE_DIR);
10
+ if (platform === "win32") {
11
+ return path.join(env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local"), "NerdBro", "Studio Agent");
12
+ }
13
+ if (platform === "darwin") {
14
+ return path.join(os.homedir(), "Library", "Application Support", "NerdBro", "Studio Agent");
15
+ }
16
+ return path.join(env.XDG_STATE_HOME || path.join(os.homedir(), ".local", "state"), "nerdbro-studio-agent");
17
+ }
18
+ export function cacheDirectory(env = process.env, platform = process.platform) {
19
+ if (env.NERDBRO_AGENT_CACHE_DIR)
20
+ return path.resolve(env.NERDBRO_AGENT_CACHE_DIR);
21
+ if (platform === "win32") {
22
+ return path.join(env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local"), "NerdBro", "Studio Agent", "Cache");
23
+ }
24
+ if (platform === "darwin")
25
+ return path.join(os.homedir(), "Library", "Caches", "NerdBro Studio Agent");
26
+ return path.join(env.XDG_CACHE_HOME || path.join(os.homedir(), ".cache"), "nerdbro-studio-agent");
27
+ }
28
+ function daemonPath() {
29
+ return path.join(stateDirectory(), "daemon.json");
30
+ }
31
+ function configPath() {
32
+ return path.join(stateDirectory(), "config.json");
33
+ }
34
+ async function atomicJson(target, value) {
35
+ await mkdir(path.dirname(target), { recursive: true, mode: 0o700 });
36
+ const temporary = `${target}.${process.pid}.tmp`;
37
+ await writeFile(temporary, `${JSON.stringify(value, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
38
+ await rename(temporary, target);
39
+ }
40
+ export async function writeDaemonMetadata(value) {
41
+ await atomicJson(daemonPath(), value);
42
+ }
43
+ export async function readDaemonMetadata() {
44
+ try {
45
+ const value = JSON.parse(await readFile(daemonPath(), "utf8"));
46
+ if (!Number.isInteger(value.pid) || !Number.isInteger(value.port) || !value.rpcSecret)
47
+ return null;
48
+ return value;
49
+ }
50
+ catch {
51
+ return null;
52
+ }
53
+ }
54
+ export async function removeDaemonMetadata() {
55
+ await unlink(daemonPath()).catch(() => undefined);
56
+ }
57
+ export function processExists(pid) {
58
+ try {
59
+ process.kill(pid, 0);
60
+ return true;
61
+ }
62
+ catch {
63
+ return false;
64
+ }
65
+ }
66
+ export async function cleanStaleDaemonMetadata() {
67
+ const metadata = await readDaemonMetadata();
68
+ if (metadata && !processExists(metadata.pid))
69
+ await removeDaemonMetadata();
70
+ }
71
+ export function validateOrigin(raw) {
72
+ let parsed;
73
+ try {
74
+ parsed = new URL(raw);
75
+ }
76
+ catch {
77
+ throw new CliError("Origin must be an absolute URL.", "AGENT_ORIGIN_INVALID", 2);
78
+ }
79
+ const local = parsed.hostname === "127.0.0.1" || parsed.hostname === "localhost" || parsed.hostname === "::1";
80
+ if (parsed.protocol !== "https:" && !(local && parsed.protocol === "http:")) {
81
+ throw new CliError("Non-local Agent origins must use HTTPS.", "AGENT_ORIGIN_INSECURE", 2);
82
+ }
83
+ if (parsed.username || parsed.password || parsed.pathname !== "/" || parsed.search || parsed.hash) {
84
+ throw new CliError("Origin must contain only scheme, host, and optional port.", "AGENT_ORIGIN_INVALID", 2);
85
+ }
86
+ return parsed.origin;
87
+ }
88
+ export async function readConfig() {
89
+ try {
90
+ const value = JSON.parse(await readFile(configPath(), "utf8"));
91
+ return { apiOrigin: validateOrigin(value.apiOrigin || DEFAULT_API_ORIGIN) };
92
+ }
93
+ catch (error) {
94
+ if (error instanceof CliError)
95
+ throw error;
96
+ return { apiOrigin: DEFAULT_API_ORIGIN };
97
+ }
98
+ }
99
+ export async function writeConfig(config) {
100
+ await atomicJson(configPath(), { apiOrigin: validateOrigin(config.apiOrigin) });
101
+ }
102
+ export async function isExecutable(file) {
103
+ try {
104
+ await access(file, constants.X_OK);
105
+ return true;
106
+ }
107
+ catch {
108
+ return false;
109
+ }
110
+ }
111
+ //# sourceMappingURL=state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.js","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACtF,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,MAAM,CAAC,MAAM,kBAAkB,GAAG,yBAAyB,CAAC;AAE5D,MAAM,UAAU,cAAc,CAAC,MAAyB,OAAO,CAAC,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,QAAQ;IAC9F,IAAI,GAAG,CAAC,uBAAuB;QAAE,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC;IAClF,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;QACzB,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,SAAS,EAAE,cAAc,CAAC,CAAC;IAC/G,CAAC;IACD,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAC1B,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,qBAAqB,EAAE,SAAS,EAAE,cAAc,CAAC,CAAC;IAC9F,CAAC;IACD,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,cAAc,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,sBAAsB,CAAC,CAAC;AAC7G,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,MAAyB,OAAO,CAAC,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,QAAQ;IAC9F,IAAI,GAAG,CAAC,uBAAuB;QAAE,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC;IAClF,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;QACzB,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,SAAS,EAAE,cAAc,EAAE,OAAO,CAAC,CAAC;IACxH,CAAC;IACD,IAAI,QAAQ,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,sBAAsB,CAAC,CAAC;IACvG,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,cAAc,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,QAAQ,CAAC,EAAE,sBAAsB,CAAC,CAAC;AACpG,CAAC;AAED,SAAS,UAAU;IACjB,OAAO,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,aAAa,CAAC,CAAC;AACpD,CAAC;AAED,SAAS,UAAU;IACjB,OAAO,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,aAAa,CAAC,CAAC;AACpD,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,MAAc,EAAE,KAAc;IACtD,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACpE,MAAM,SAAS,GAAG,GAAG,MAAM,IAAI,OAAO,CAAC,GAAG,MAAM,CAAC;IACjD,MAAM,SAAS,CAAC,SAAS,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACrG,MAAM,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;AAClC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,KAAqB;IAC7D,MAAM,UAAU,CAAC,UAAU,EAAE,EAAE,KAAK,CAAC,CAAC;AACxC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB;IACtC,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,UAAU,EAAE,EAAE,MAAM,CAAC,CAAmB,CAAC;QACjF,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS;YAAE,OAAO,IAAI,CAAC;QACnG,OAAO,KAAK,CAAC;IACf,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,oBAAoB;IACxC,MAAM,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;AACpD,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,GAAW;IACvC,IAAI,CAAC;QACH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,wBAAwB;IAC5C,MAAM,QAAQ,GAAG,MAAM,kBAAkB,EAAE,CAAC;IAC5C,IAAI,QAAQ,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,MAAM,oBAAoB,EAAE,CAAC;AAC7E,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,IAAI,MAAW,CAAC;IAChB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IACxB,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,QAAQ,CAAC,iCAAiC,EAAE,sBAAsB,EAAE,CAAC,CAAC,CAAC;IACnF,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,KAAK,WAAW,IAAI,MAAM,CAAC,QAAQ,KAAK,WAAW,IAAI,MAAM,CAAC,QAAQ,KAAK,KAAK,CAAC;IAC9G,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,IAAI,CAAC,CAAC,KAAK,IAAI,MAAM,CAAC,QAAQ,KAAK,OAAO,CAAC,EAAE,CAAC;QAC5E,MAAM,IAAI,QAAQ,CAAC,yCAAyC,EAAE,uBAAuB,EAAE,CAAC,CAAC,CAAC;IAC5F,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,QAAQ,KAAK,GAAG,IAAI,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;QAClG,MAAM,IAAI,QAAQ,CAAC,2DAA2D,EAAE,sBAAsB,EAAE,CAAC,CAAC,CAAC;IAC7G,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC;AACvB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU;IAC9B,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,UAAU,EAAE,EAAE,MAAM,CAAC,CAAyB,CAAC;QACvF,OAAO,EAAE,SAAS,EAAE,cAAc,CAAC,KAAK,CAAC,SAAS,IAAI,kBAAkB,CAAC,EAAE,CAAC;IAC9E,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,QAAQ;YAAE,MAAM,KAAK,CAAC;QAC3C,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,CAAC;IAC3C,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,MAAmB;IACnD,MAAM,UAAU,CAAC,UAAU,EAAE,EAAE,EAAE,SAAS,EAAE,cAAc,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;AAClF,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAY;IAC7C,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC;QACnC,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
@@ -0,0 +1,64 @@
1
+ export type Json = null | boolean | number | string | Json[] | {
2
+ [key: string]: Json;
3
+ };
4
+ export interface DaemonMetadata {
5
+ pid: number;
6
+ port: number;
7
+ rpcSecret: string;
8
+ startedAt: string;
9
+ headless: boolean;
10
+ }
11
+ export interface AgentConfig {
12
+ apiOrigin: string;
13
+ }
14
+ export interface CommandResult {
15
+ ok: boolean;
16
+ command: string;
17
+ [key: string]: unknown;
18
+ }
19
+ export interface ApiEnvelope<T = unknown> {
20
+ data: T;
21
+ detail?: string;
22
+ pagination?: unknown;
23
+ }
24
+ export declare const EXIT_CODE: {
25
+ readonly failure: 1;
26
+ readonly usage: 2;
27
+ readonly conflict: 3;
28
+ readonly authentication: 4;
29
+ readonly environment: 5;
30
+ };
31
+ export interface StudioLock {
32
+ schema: "kheleshikhi.interactive-studio-lock.v1";
33
+ asset_id: string;
34
+ server_origin: string;
35
+ base_snapshot_public_id: string | null;
36
+ base_revision_public_id: string | null;
37
+ source_hash: string;
38
+ checkout_source_hash: string;
39
+ compiler_version: string;
40
+ scene_schema: unknown;
41
+ component_catalogue_checksum: string;
42
+ behavior_catalogue_checksum: string;
43
+ checkout_time: string;
44
+ }
45
+ export declare const LIBRARY_KINDS: readonly ["experience", "audio", "component", "behavior"];
46
+ export type LibraryKind = (typeof LIBRARY_KINDS)[number];
47
+ /** Local optimistic-concurrency record for one checked-out Library definition. */
48
+ export interface LibraryLock {
49
+ schema: "kheleshikhi.library-lock.v1";
50
+ kind: LibraryKind;
51
+ stable_id: string;
52
+ server_origin: string;
53
+ head_revision_public_id: string | null;
54
+ source_hash: string;
55
+ schema_version: string | null;
56
+ checked_out_at: string;
57
+ updated_at?: string;
58
+ }
59
+ export declare class CliError extends Error {
60
+ readonly code: string;
61
+ readonly exitCode: number;
62
+ readonly details: unknown;
63
+ constructor(message: string, code?: string, exitCode?: number, details?: unknown);
64
+ }
package/dist/types.js ADDED
@@ -0,0 +1,20 @@
1
+ export const EXIT_CODE = {
2
+ failure: 1,
3
+ usage: 2,
4
+ conflict: 3,
5
+ authentication: 4,
6
+ environment: 5,
7
+ };
8
+ export const LIBRARY_KINDS = ["experience", "audio", "component", "behavior"];
9
+ export class CliError extends Error {
10
+ code;
11
+ exitCode;
12
+ details;
13
+ constructor(message, code = "AGENT_COMMAND_FAILED", exitCode = 1, details = {}) {
14
+ super(message);
15
+ this.code = code;
16
+ this.exitCode = exitCode;
17
+ this.details = details;
18
+ }
19
+ }
20
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AA0BA,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,OAAO,EAAE,CAAC;IACV,KAAK,EAAE,CAAC;IACR,QAAQ,EAAE,CAAC;IACX,cAAc,EAAE,CAAC;IACjB,WAAW,EAAE,CAAC;CACN,CAAC;AAiBX,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,YAAY,EAAE,OAAO,EAAE,WAAW,EAAE,UAAU,CAAU,CAAC;AAgBvF,MAAM,OAAO,QAAS,SAAQ,KAAK;IAGtB;IACA;IACA;IAJX,YACE,OAAe,EACN,OAAO,sBAAsB,EAC7B,WAAW,CAAC,EACZ,UAAmB,EAAE;QAE9B,KAAK,CAAC,OAAO,CAAC,CAAC;QAJN,SAAI,GAAJ,IAAI,CAAyB;QAC7B,aAAQ,GAAR,QAAQ,CAAI;QACZ,YAAO,GAAP,OAAO,CAAc;IAGhC,CAAC;CACF"}
@@ -0,0 +1,15 @@
1
+ import type { StudioLock } from "./types.js";
2
+ export declare function canonicalJson(value: unknown): string;
3
+ export declare function sourceHash(value: unknown): string;
4
+ export declare class StudioWorkspace {
5
+ readonly root: string;
6
+ readonly asset: string;
7
+ readonly directory: string;
8
+ readonly sourcePath: string;
9
+ readonly lockPath: string;
10
+ constructor(root: string, asset: string);
11
+ source(): Promise<Record<string, unknown>>;
12
+ lock(): Promise<StudioLock>;
13
+ write(source: Record<string, unknown>, lock: StudioLock): Promise<void>;
14
+ writeLock(lock: StudioLock): Promise<void>;
15
+ }
@@ -0,0 +1,66 @@
1
+ import { createHash } from "node:crypto";
2
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { CliError } from "./types.js";
5
+ const ASSET_ID = /^[A-Za-z0-9_-]+$/;
6
+ export function canonicalJson(value) {
7
+ if (Array.isArray(value))
8
+ return `[${value.map(canonicalJson).join(",")}]`;
9
+ if (value && typeof value === "object") {
10
+ const record = value;
11
+ return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(",")}}`;
12
+ }
13
+ return JSON.stringify(value);
14
+ }
15
+ export function sourceHash(value) {
16
+ return createHash("sha256").update(canonicalJson(value)).digest("hex");
17
+ }
18
+ export class StudioWorkspace {
19
+ root;
20
+ asset;
21
+ directory;
22
+ sourcePath;
23
+ lockPath;
24
+ constructor(root, asset) {
25
+ this.root = root;
26
+ this.asset = asset;
27
+ if (!ASSET_ID.test(asset))
28
+ throw new CliError("Asset id contains unsafe path characters.", "STUDIO_ASSET_ID_INVALID", 2);
29
+ this.directory = path.resolve(root, asset);
30
+ if (path.dirname(this.directory) !== path.resolve(root))
31
+ throw new CliError("Checkout escaped its workspace.", "STUDIO_WORKSPACE_ESCAPE", 2);
32
+ this.sourcePath = path.join(this.directory, "source.json");
33
+ this.lockPath = path.join(this.directory, "studio-lock.json");
34
+ }
35
+ async source() {
36
+ try {
37
+ const value = JSON.parse(await readFile(this.sourcePath, "utf8"));
38
+ if (!value || typeof value !== "object" || Array.isArray(value))
39
+ throw new Error("not an object");
40
+ return value;
41
+ }
42
+ catch (error) {
43
+ throw new CliError(`Cannot read ${this.sourcePath}: ${String(error)}`, "STUDIO_SOURCE_INVALID", 2);
44
+ }
45
+ }
46
+ async lock() {
47
+ try {
48
+ const value = JSON.parse(await readFile(this.lockPath, "utf8"));
49
+ if (value.asset_id !== this.asset || value.schema !== "kheleshikhi.interactive-studio-lock.v1")
50
+ throw new Error("wrong asset or schema");
51
+ return value;
52
+ }
53
+ catch (error) {
54
+ throw new CliError(`Cannot read ${this.lockPath}: ${String(error)}`, "STUDIO_LOCK_INVALID", 2);
55
+ }
56
+ }
57
+ async write(source, lock) {
58
+ await mkdir(this.directory, { recursive: true });
59
+ await writeFile(this.sourcePath, `${JSON.stringify(source, null, 2)}\n`, "utf8");
60
+ await writeFile(this.lockPath, `${JSON.stringify(lock, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
61
+ }
62
+ async writeLock(lock) {
63
+ await writeFile(this.lockPath, `${JSON.stringify(lock, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
64
+ }
65
+ }
66
+ //# sourceMappingURL=workspace.js.map