| 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 | */ |
| 8 | import * as HostProcess from "@t3tools/shared/HostProcess"; |
| 9 | import { resolveNodeExecutable } from "@t3tools/shared/nodeRuntime"; |
| 10 | import * as Effect from "effect/Effect"; |
| 11 | import * as FileSystem from "effect/FileSystem"; |
| 12 | import * as Path from "effect/Path"; |
| 13 | |
| 14 | const SHIM_DIR = "device/bin"; |
| 15 | |
| 16 | export 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"; |
| 31 | const args = process.argv.slice(2); |
| 32 | const informational = args.length === 1 && ["help", "--help", "-h", "--version", "version"].includes(args[0]); |
| 33 | const hasValue = flag => { const index = args.indexOf(flag); return index >= 0 && !!args[index + 1] && !args[index + 1].startsWith("--"); }; |
| 34 | if (!informational && !(hasValue("--config") && hasValue("--session"))) { |
| 35 | console.error("Call device_open first and include its --config and --session flags."); |
| 36 | process.exit(1); |
| 37 | } |
| 38 | const env = { ...process.env }; |
| 39 | delete env.AGENT_DEVICE_DAEMON_BASE_URL; |
| 40 | delete env.AGENT_DEVICE_DAEMON_AUTH_TOKEN; |
| 41 | delete env.AGENT_DEVICE_CONFIG; |
| 42 | const child = spawn(${JSON.stringify(node)}, [${JSON.stringify(entryPath)}, ...args], { stdio: "inherit", env }); |
| 43 | child.on("error", error => { console.error(error.message); process.exitCode = 1; }); |
| 44 | child.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 | }); |