@tarfa/cli 0.1.4 → 0.1.5

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.
@@ -4432,8 +4432,9 @@ var TmuxAdapter = class {
4432
4432
  return set;
4433
4433
  }
4434
4434
  async createSession(input) {
4435
+ const env = Object.entries(input.env ?? {}).flatMap(([k, v]) => ["-e", `${k}=${v}`]);
4435
4436
  try {
4436
- await this.tmux(["new-session", "-d", "-s", input.name, "-c", input.cwd, input.command]);
4437
+ await this.tmux(["new-session", "-d", "-s", input.name, "-c", input.cwd, ...env, input.command]);
4437
4438
  } catch (err) {
4438
4439
  if ((err.stderr ?? "").includes("duplicate session")) throw new DuplicateSessionError(input.name);
4439
4440
  throw err;
@@ -5878,12 +5879,12 @@ function loginShellResolver(shell = process.env.SHELL || "/bin/sh") {
5878
5879
  }
5879
5880
 
5880
5881
  // src/infrastructure/codex-app-server.ts
5881
- import { homedir } from "node:os";
5882
+ import { realpath as realpath3 } from "node:fs/promises";
5882
5883
  import { join as join7 } from "node:path";
5883
5884
  import { WebSocket } from "ws";
5884
5885
  var CodexAppServerDown = class extends Error {
5885
- constructor() {
5886
- super("the Codex app server is not running");
5886
+ constructor(socketPath) {
5887
+ super(`the Codex app server is not running (no socket at ${socketPath})`);
5887
5888
  }
5888
5889
  };
5889
5890
  var CodexRpcError = class extends Error {
@@ -5954,8 +5955,8 @@ var Connection = class {
5954
5955
  var CodexAppServer = class {
5955
5956
  socketPath;
5956
5957
  timeoutMs;
5957
- constructor(options = {}) {
5958
- this.socketPath = options.socketPath ?? join7(homedir(), ".codex", "app-server-control", "app-server-control.sock");
5958
+ constructor(options) {
5959
+ this.socketPath = "socketPath" in options ? options.socketPath : join7(options.codexHome, "app-server-control", "app-server-control.sock");
5959
5960
  this.timeoutMs = options.timeoutMs ?? 5e3;
5960
5961
  }
5961
5962
  loadedThreads() {
@@ -6009,9 +6010,15 @@ var CodexAppServer = class {
6009
6010
  return thread.id;
6010
6011
  });
6011
6012
  }
6012
- call(run) {
6013
+ // Codex 0.167 binds the socket under /tmp/codex-daemon-<uid>/ and publishes this path as a link
6014
+ // to it. Connecting to the link's target keeps the path within the 107 bytes a unix socket
6015
+ // address holds, however deep CODEX_HOME is; a link to nothing means no daemon.
6016
+ async call(run) {
6017
+ const target = await realpath3(this.socketPath).catch((err) => {
6018
+ throw err.code === "ENOENT" ? new CodexAppServerDown(this.socketPath) : err;
6019
+ });
6013
6020
  return new Promise((resolve6, reject) => {
6014
- const ws = new WebSocket(`ws+unix://${this.socketPath}:/`, { perMessageDeflate: false });
6021
+ const ws = new WebSocket(`ws+unix://${target}:/`, { perMessageDeflate: false });
6015
6022
  const c = new Connection(ws);
6016
6023
  let settled = false;
6017
6024
  const settle = (fn) => {
@@ -6030,7 +6037,7 @@ var CodexAppServer = class {
6030
6037
  }, this.timeoutMs);
6031
6038
  ws.once("error", (err) => {
6032
6039
  const down = err.code === "ENOENT" || err.code === "ECONNREFUSED";
6033
- settle(() => reject(down ? new CodexAppServerDown() : err));
6040
+ settle(() => reject(down ? new CodexAppServerDown(this.socketPath) : err));
6034
6041
  });
6035
6042
  ws.once("close", () => settle(() => reject(new Error("the Codex app server closed the connection"))));
6036
6043
  ws.once("open", () => {
@@ -6046,6 +6053,45 @@ var CodexAppServer = class {
6046
6053
  }
6047
6054
  };
6048
6055
 
6056
+ // src/infrastructure/codex-versions.ts
6057
+ import { execFile as execFile5 } from "node:child_process";
6058
+ function loginShell(shell = process.env.SHELL || "/bin/sh", extraEnv = {}) {
6059
+ return (script) => new Promise((resolve6, reject) => {
6060
+ execFile5(shell, ["-lc", script], { env: { ...childEnv(), ...extraEnv }, timeout: 15e3 }, (err, stdout) => err ? reject(err) : resolve6(stdout));
6061
+ });
6062
+ }
6063
+ function codexVersions(run = loginShell()) {
6064
+ return async () => {
6065
+ const [cliOut, daemonOut] = await Promise.all([
6066
+ run("codex --version").catch((err) => {
6067
+ throw new Error(`cannot run codex --version: ${err.message}`);
6068
+ }),
6069
+ run("codex app-server daemon version").catch((err) => {
6070
+ throw new Error(`cannot read the Codex app server's version: ${err.message}`);
6071
+ })
6072
+ ]);
6073
+ const cli = cliOut.trim().split(/\s+/).pop() ?? "";
6074
+ let appServer;
6075
+ try {
6076
+ appServer = JSON.parse(daemonOut).appServerVersion;
6077
+ } catch {
6078
+ appServer = void 0;
6079
+ }
6080
+ if (typeof appServer !== "string" || appServer === "") throw new Error("the Codex app server is not running");
6081
+ return { cli, appServer };
6082
+ };
6083
+ }
6084
+
6085
+ // src/infrastructure/codex-home.ts
6086
+ import { join as join8 } from "node:path";
6087
+ var MARK = "codex-home=";
6088
+ async function resolveCodexHome(env, home, run = loginShell()) {
6089
+ if (env.CODEX_HOME) return env.CODEX_HOME;
6090
+ const out = await run(`printf '\\n${MARK}%s\\n' "\${CODEX_HOME-}"`).catch(() => "");
6091
+ const value = out.split("\n").reverse().find((line) => line.startsWith(MARK))?.slice(MARK.length);
6092
+ return value || join8(home, ".codex");
6093
+ }
6094
+
6049
6095
  export {
6050
6096
  packageRoot,
6051
6097
  packageVersion,
@@ -6149,5 +6195,8 @@ export {
6149
6195
  detectInstalled,
6150
6196
  loginShellResolver,
6151
6197
  CodexAppServerDown,
6152
- CodexAppServer
6198
+ CodexAppServer,
6199
+ loginShell,
6200
+ codexVersions,
6201
+ resolveCodexHome
6153
6202
  };
package/dist/cli.js CHANGED
@@ -22,6 +22,7 @@ import {
22
22
  browseDir,
23
23
  childEnv,
24
24
  cloneOfUnit,
25
+ codexVersions,
25
26
  createWithFreshId,
26
27
  createWorkspace,
27
28
  describeStep,
@@ -41,6 +42,7 @@ import {
41
42
  loadConfig,
42
43
  loadOrCreateAgentKeys,
43
44
  locateAgentEnv,
45
+ loginShell,
44
46
  loginShellResolver,
45
47
  mainCheckout,
46
48
  migrateStateDir,
@@ -53,6 +55,7 @@ import {
53
55
  planCli,
54
56
  planSetup,
55
57
  renderService,
58
+ resolveCodexHome,
56
59
  resolveStateDir,
57
60
  revokeDevice,
58
61
  sanitizeDeviceName,
@@ -63,7 +66,7 @@ import {
63
66
  unitRunsLauncher,
64
67
  withEnrolmentMode,
65
68
  withLegacyEnv
66
- } from "./chunk-OC5VX7K3.js";
69
+ } from "./chunk-KTUXNG4Q.js";
67
70
 
68
71
  // src/cli.ts
69
72
  import { randomBytes } from "node:crypto";
@@ -241,10 +244,9 @@ async function readOrNull(path) {
241
244
  throw err;
242
245
  }
243
246
  }
244
- function hooksFiles(home, hooksDir, port) {
247
+ function hooksFiles(home, hooksDir, port, codexHome) {
245
248
  const settingsPath = join(home, ".claude", "settings.json");
246
- const codexDir = join(home, ".codex");
247
- const codexHooksPath = join(codexDir, "hooks.json");
249
+ const codexHooksPath = join(codexHome, "hooks.json");
248
250
  async function wanted() {
249
251
  const files = [];
250
252
  for (const script of SCRIPTS) {
@@ -254,7 +256,7 @@ function hooksFiles(home, hooksDir, port) {
254
256
  const settings = await readOrNull(settingsPath);
255
257
  const merged = installHooks({ settings: settings === null ? {} : JSON.parse(settings) }, { hookCommand: HOOK_COMMAND, guardCommand: GUARD_COMMAND, port });
256
258
  files.push({ path: settingsPath, current: settings, text: JSON.stringify(merged, null, 2) });
257
- if (await fsIsDirectory(codexDir)) {
259
+ if (await fsIsDirectory(codexHome)) {
258
260
  const codex = await readOrNull(codexHooksPath);
259
261
  const codexMerged = installHooks(
260
262
  { settings: codex === null ? {} : JSON.parse(codex) },
@@ -1078,12 +1080,13 @@ async function spawnCodex(deps, input) {
1078
1080
  firstMessage: input.brief.replace(/\n+$/, "")
1079
1081
  });
1080
1082
  if (!/^[0-9A-Za-z-]+$/.test(thread)) throw new Error(`the Codex app server named an unexpected thread id: ${thread}`);
1083
+ const codexHome = await deps.codex.home();
1081
1084
  const lockTitle = await titleLock(deps, title);
1082
1085
  const command = `codex resume ${thread}`;
1083
1086
  const id = await createWithFreshId(
1084
1087
  deps.ids,
1085
1088
  cwd,
1086
- (id2) => deps.tmux.createSession({ name: id2, cwd, command, codexThread: thread, ...parent ? { parent: parent.id } : {} })
1089
+ (id2) => deps.tmux.createSession({ name: id2, cwd, command, codexThread: thread, env: { CODEX_HOME: codexHome }, ...parent ? { parent: parent.id } : {} })
1087
1090
  );
1088
1091
  return finishLaunch(deps, id, title, lockTitle, ` (Codex thread ${thread})`);
1089
1092
  }
@@ -1133,35 +1136,6 @@ function denySettings(denyPath) {
1133
1136
  };
1134
1137
  }
1135
1138
 
1136
- // src/infrastructure/codex-versions.ts
1137
- import { execFile as execFile6 } from "node:child_process";
1138
- function loginShell(shell = process.env.SHELL || "/bin/sh") {
1139
- return (script) => new Promise((resolve2, reject) => {
1140
- execFile6(shell, ["-lc", script], { env: childEnv(), timeout: 15e3 }, (err, stdout) => err ? reject(err) : resolve2(stdout));
1141
- });
1142
- }
1143
- function codexVersions(run4 = loginShell()) {
1144
- return async () => {
1145
- const [cliOut, daemonOut] = await Promise.all([
1146
- run4("codex --version").catch((err) => {
1147
- throw new Error(`cannot run codex --version: ${err.message}`);
1148
- }),
1149
- run4("codex app-server daemon version").catch((err) => {
1150
- throw new Error(`cannot read the Codex app server's version: ${err.message}`);
1151
- })
1152
- ]);
1153
- const cli = cliOut.trim().split(/\s+/).pop() ?? "";
1154
- let appServer;
1155
- try {
1156
- appServer = JSON.parse(daemonOut).appServerVersion;
1157
- } catch {
1158
- appServer = void 0;
1159
- }
1160
- if (typeof appServer !== "string" || appServer === "") throw new Error("the Codex app server is not running");
1161
- return { cli, appServer };
1162
- };
1163
- }
1164
-
1165
1139
  // src/application/workspace-role.ts
1166
1140
  var WORKSPACE_ROLES = ["orchestrator", "child", "normal"];
1167
1141
  function workspaceRole(w, all) {
@@ -1266,7 +1240,7 @@ async function run3(argv, rawEnv, home, io = {}) {
1266
1240
  return JSON.stringify(await browseDir({ lister, hidden }, path, config.projectRoots), null, 2);
1267
1241
  }
1268
1242
  case "install-hooks": {
1269
- const hooks = hooksFiles(home, HOOKS_DIR, config.port);
1243
+ const hooks = hooksFiles(home, HOOKS_DIR, config.port, await resolveCodexHome({ ...env, ...await agentEnvValues(env, home) }, home, io.loginShell));
1270
1244
  const { codex } = await hooks.install();
1271
1245
  if (!codex) return JSON.stringify({ installed: hooks.settingsPath, scriptPath: hooks.scriptPath });
1272
1246
  return JSON.stringify({
@@ -1413,7 +1387,7 @@ async function orchestration(command, argv, env, home, io) {
1413
1387
  nonce: () => randomBytes(6).toString("hex"),
1414
1388
  sleep: io.sleep ?? ((ms) => new Promise((resolve2) => setTimeout(resolve2, ms))),
1415
1389
  spawnDir: joinPath(config.stateDir, "spawn"),
1416
- codex: io.codex ?? { versions: codexVersions(), createThread: (input) => new CodexAppServer().createThread(input) }
1390
+ codex: io.codex ?? codexSpawn({ ...env, ...values }, home, io)
1417
1391
  },
1418
1392
  {
1419
1393
  projectPath,
@@ -1633,6 +1607,15 @@ function pwaOrigin(value) {
1633
1607
  if (url.protocol !== "https:" && url.protocol !== "http:") throw new Error(`--pwa must be an http(s) origin (got ${value})`);
1634
1608
  return url.origin;
1635
1609
  }
1610
+ function codexSpawn(env, home, io) {
1611
+ let codexHome;
1612
+ const resolve2 = () => codexHome ??= resolveCodexHome(env, home, io.loginShell);
1613
+ return {
1614
+ home: resolve2,
1615
+ versions: async () => codexVersions(loginShell(void 0, { CODEX_HOME: await resolve2() }))(),
1616
+ createThread: async (input) => new CodexAppServer({ codexHome: await resolve2() }).createThread(input)
1617
+ };
1618
+ }
1636
1619
  async function agentEnvValues(env, home) {
1637
1620
  try {
1638
1621
  return withLegacyEnv(parseEnvFile(await readFile6(await locateAgentEnv(env, home), "utf8")));
@@ -1790,7 +1773,7 @@ ${SETUP_USAGE.split("\n")[0]}
1790
1773
  const config = loadConfig({ ...withLegacyEnv(parseEnvFile(envText ?? "")), TARFA_MACHINE_ID: flags.machineId }, home);
1791
1774
  const tailscale = fx.tailscale ?? tailscaleCli();
1792
1775
  const owner = fx.portOwner ?? portOwner;
1793
- const hooks = hooksFiles(home, HOOKS_DIR, config.port);
1776
+ const hooks = hooksFiles(home, HOOKS_DIR, config.port, await resolveCodexHome({ ...env, ...withLegacyEnv(parseEnvFile(envText ?? "")) }, home, io.loginShell));
1794
1777
  const devices2 = new FileDeviceStore(config.devicesPath);
1795
1778
  const hasRoster = await readFile6(config.devicesPath).then(() => true, () => false);
1796
1779
  const agentLivePath = cloneRepo === null ? "" : joinPath(cloneRepo, "deploy", "agent-live");
@@ -1916,7 +1899,7 @@ async function apply2(argv, env, home, io) {
1916
1899
  if (envText === null) throw new Error(`failed: check: no agent config at ${envPath}: run tarfa setup`);
1917
1900
  const config = loadConfig(withLegacyEnv(parseEnvFile(envText)), home);
1918
1901
  const service = fx.service ?? serviceInstall({ home, user: userInfo().username, uid: process.getuid?.() ?? 0 });
1919
- const hooks = hooksFiles(home, HOOKS_DIR, config.port);
1902
+ const hooks = hooksFiles(home, HOOKS_DIR, config.port, await resolveCodexHome({ ...env, ...withLegacyEnv(parseEnvFile(envText)) }, home, io.loginShell));
1920
1903
  const launcherPath = joinPath(home, ".tarfa", "bin", "tarfa-agent");
1921
1904
  const log = fx.agentLog ?? agentLog({ home });
1922
1905
  const result = await runApply(
package/dist/serve.js CHANGED
@@ -65,6 +65,7 @@ import {
65
65
  readHandshakeFrame,
66
66
  redeemPairing,
67
67
  relationOf,
68
+ resolveCodexHome,
68
69
  runsClaude,
69
70
  sessionOfCodexThread,
70
71
  standingOf,
@@ -73,7 +74,7 @@ import {
73
74
  systemPathResolver,
74
75
  withLegacyEnv,
75
76
  writeFileAtomic
76
- } from "./chunk-OC5VX7K3.js";
77
+ } from "./chunk-KTUXNG4Q.js";
77
78
 
78
79
  // src/serve.ts
79
80
  import { homedir as homedir2 } from "node:os";
@@ -4185,6 +4186,9 @@ async function main() {
4185
4186
  const env = withLegacyEnv(process.env);
4186
4187
  const config = loadConfig(env, homedir2());
4187
4188
  for (const warning of configWarnings(process.env)) process.stderr.write(`${warning}
4189
+ `);
4190
+ const codexHome = await resolveCodexHome(env, homedir2());
4191
+ process.stdout.write(`tarfa agent: Codex home ${codexHome}
4188
4192
  `);
4189
4193
  await securePrivateDir(config.stateDir);
4190
4194
  await assertPrivateFiles([
@@ -4241,7 +4245,7 @@ async function main() {
4241
4245
  delivery: new CompositeDelivery({
4242
4246
  claude: new ClaudeInboxDelivery(claudeSessions, async (id) => await tmux.firstPaneId?.(id) ?? null),
4243
4247
  pi: new PiInboxDelivery(config.inboxDir),
4244
- codex: new CodexDelivery(new CodexAppServer())
4248
+ codex: new CodexDelivery(new CodexAppServer({ codexHome }))
4245
4249
  }),
4246
4250
  log: new FileMessageLog(config.messageLogPath)
4247
4251
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tarfa/cli",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Tarfa agent and CLI: serves your Claude Code terminal sessions (tmux workspaces) to the Tarfa phone app.",
5
5
  "repository": {
6
6
  "type": "git",