1// @effect-diagnostics preferSchemaOverJson:off - JSON string literals embed paths safely into generated JavaScript.
2/**
3 * A directory holding an `agent-device` launcher that runs the pinned install
4 * with a Node runtime. Prepended to provider subprocess PATHs so the agent
5 * types `agent-device …` and gets the version the injected instructions were
6 * written for, regardless of what is or is not globally installed.
7 */
8import * as HostProcess from "@t3tools/shared/HostProcess";
9import { resolveNodeExecutable } from "@t3tools/shared/nodeRuntime";
10import * as Effect from "effect/Effect";
11import * as FileSystem from "effect/FileSystem";
12import * as Path from "effect/Path";
13
14const SHIM_DIR = "device/bin";
15
16export const ensureAgentDeviceShim = Effect.fn("AgentDeviceShim.ensure")(function* (input: {
17 readonly entryPath: string;
18 readonly stateDir: string;
19}) {
20 const { entryPath } = input;
21 const fs = yield* FileSystem.FileSystem;
22 const path = yield* Path.Path;
23 const platform = yield* HostProcess.Platform;
24 const node = yield* resolveNodeExecutable("Device automation");
25 const shimDir = path.join(input.stateDir, SHIM_DIR);
26 yield* fs.makeDirectory(shimDir, { recursive: true });
27 const launcherPath = path.join(shimDir, "agent-device-launcher.mjs");
28 yield* fs.writeFileString(
29 launcherPath,
30 `import { spawn } from "node:child_process";
31const args = process.argv.slice(2);
32const informational = args.length === 1 && ["help", "--help", "-h", "--version", "version"].includes(args[0]);
33const hasValue = flag => { const index = args.indexOf(flag); return index >= 0 && !!args[index + 1] && !args[index + 1].startsWith("--"); };
34if (!informational && !(hasValue("--config") && hasValue("--session"))) {
35 console.error("Call device_open first and include its --config and --session flags.");
36 process.exit(1);
37}
38const env = { ...process.env };
39delete env.AGENT_DEVICE_DAEMON_BASE_URL;
40delete env.AGENT_DEVICE_DAEMON_AUTH_TOKEN;
41delete env.AGENT_DEVICE_CONFIG;
42const child = spawn(${JSON.stringify(node)}, [${JSON.stringify(entryPath)}, ...args], { stdio: "inherit", env });
43child.on("error", error => { console.error(error.message); process.exitCode = 1; });
44child.on("exit", code => { process.exitCode = code ?? 1; });
45`,
46 );
47 if (platform === "win32") {
48 const script = `@echo off\r\n"${node}" "${launcherPath}" %*\r\n`;
49 yield* fs.writeFileString(path.join(shimDir, "agent-device.cmd"), script);
50 } else {
51 const command = [node, launcherPath]
52 .map((value) => "'" + value.replaceAll("'", "'\"'\"'") + "'")
53 .join(" ");
54 const script = `#!/bin/sh\nexec ${command} "$@"\n`;
55 const shimPath = path.join(shimDir, "agent-device");
56 yield* fs.writeFileString(shimPath, script);
57 yield* fs.chmod(shimPath, 0o755);
58 }
59 return shimDir;
60});