@openscout/scout 0.2.69 → 0.2.70

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.
@@ -7,9 +7,9 @@ import { createRequire as createRequire2 } from "node:module";
7
7
 
8
8
  // packages/web/server/terminal-relay-session.ts
9
9
  import { createRequire } from "module";
10
- import { execFileSync, execSync } from "child_process";
11
- import { accessSync, constants, existsSync, mkdirSync, writeFileSync } from "fs";
12
- import { delimiter as pathDelimiter, join, dirname as pathDirname, resolve as pathResolve } from "path";
10
+ import { execSync } from "child_process";
11
+ import { existsSync, mkdirSync, writeFileSync } from "fs";
12
+ import { join, dirname as pathDirname } from "path";
13
13
  var require2 = createRequire(import.meta.url);
14
14
  var pty = require2("node-pty");
15
15
  var DEFAULT_ORPHAN_TTL_MS = 30 * 60 * 1000;
@@ -56,9 +56,6 @@ function resizeSession(session, cols, rows) {
56
56
  return false;
57
57
  try {
58
58
  session.pty.resize(cols, rows);
59
- if (session.backend === "tmux" && session.tmuxSession) {
60
- resizeTmuxWindow(session.tmuxSession, cols, rows);
61
- }
62
59
  session.cols = cols;
63
60
  session.rows = rows;
64
61
  return true;
@@ -93,78 +90,17 @@ function findBin(name, envOverride) {
93
90
  return null;
94
91
  }
95
92
  }
96
- function expandHomePath(value) {
97
- const home = process.env.HOME || "/tmp";
98
- if (value === "~")
99
- return home;
100
- if (value.startsWith("~/"))
101
- return join(home, value.slice(2));
102
- return value;
103
- }
104
- function isExecutablePath(candidate) {
105
- if (!candidate)
106
- return false;
107
- try {
108
- accessSync(candidate, constants.X_OK);
109
- return true;
110
- } catch {
111
- return false;
112
- }
113
- }
114
- function findExecutableInDirectories(name, directories) {
115
- const seen = new Set;
116
- for (const directory of directories) {
117
- if (!directory)
118
- continue;
119
- const normalizedDirectory = pathResolve(expandHomePath(directory));
120
- if (seen.has(normalizedDirectory))
121
- continue;
122
- seen.add(normalizedDirectory);
123
- const candidate = join(normalizedDirectory, name);
124
- if (isExecutablePath(candidate))
125
- return candidate;
126
- }
127
- return null;
128
- }
129
- function findExecutableOnPath(name) {
130
- return findExecutableInDirectories(name, (process.env.PATH || "").split(pathDelimiter));
131
- }
132
93
  function findClaudeBin() {
133
- for (const envKey of ["OPENSCOUT_CLAUDE_BIN", "SCOUT_CLAUDE_BIN", "CLAUDE_BIN"]) {
134
- const explicit = process.env[envKey]?.trim();
135
- if (!explicit)
136
- continue;
137
- const expanded = expandHomePath(explicit);
138
- if (isExecutablePath(expanded))
139
- return pathResolve(expanded);
140
- const foundOnPath = findExecutableOnPath(explicit);
141
- if (foundOnPath)
142
- return foundOnPath;
143
- }
144
- const home = process.env.HOME || "/tmp";
145
- return findExecutableInDirectories("claude", [
146
- join(home, ".local", "bin"),
147
- join(home, ".claude", "local"),
148
- "/opt/homebrew/bin",
149
- "/usr/local/bin",
150
- join(home, ".bun", "bin")
151
- ]) ?? findExecutableOnPath("claude");
94
+ return findBin("claude", "CLAUDE_BIN");
152
95
  }
153
96
  function findPiBin() {
154
97
  return findBin("pi", "PI_BIN");
155
98
  }
156
99
  function findShellBin() {
157
- const candidates = [
158
- process.env.SHELL,
159
- "/bin/zsh",
160
- "/bin/bash",
161
- "/bin/sh"
162
- ].filter((candidate) => Boolean(candidate));
163
- for (const candidate of candidates) {
164
- if (existsSync(candidate))
165
- return candidate;
166
- }
167
- return null;
100
+ const configured = process.env.SHELL;
101
+ if (configured && existsSync(configured))
102
+ return configured;
103
+ return findBin("zsh") ?? findBin("bash") ?? findBin("sh") ?? (existsSync("/bin/sh") ? "/bin/sh" : null);
168
104
  }
169
105
  function normalizePiProviderForCli(provider) {
170
106
  if (!provider)
@@ -181,22 +117,6 @@ function tmuxSessionExists(name) {
181
117
  return false;
182
118
  }
183
119
  }
184
- function resizeTmuxWindow(name, cols, rows) {
185
- try {
186
- execFileSync("tmux", [
187
- "resize-window",
188
- "-t",
189
- name,
190
- "-x",
191
- String(cols),
192
- "-y",
193
- String(rows)
194
- ], { stdio: "ignore" });
195
- return true;
196
- } catch {
197
- return false;
198
- }
199
- }
200
120
  function bootstrapFiles(cwd, files, sessionId) {
201
121
  for (const [relPath, content] of Object.entries(files)) {
202
122
  const absPath = join(cwd, relPath);
@@ -213,14 +133,16 @@ function bootstrapFiles(cwd, files, sessionId) {
213
133
  }
214
134
  }
215
135
  }
216
- function spawnTmuxSession(tmuxName, cols, rows, cwd, claudeBin, claudeArgs, env) {
136
+ function spawnTmuxSession(tmuxName, cols, rows, cwd, commandBin, commandArgs, env) {
217
137
  const exists = tmuxSessionExists(tmuxName);
218
138
  if (!exists) {
219
- const shellCmd = [claudeBin, ...claudeArgs].map((a) => a.includes(" ") ? `'${a}'` : a).join(" ");
139
+ const shellCmd = [commandBin, ...commandArgs].map((a) => a.includes(" ") ? `'${a}'` : a).join(" ");
220
140
  execSync(`tmux new-session -d -s ${tmuxName} -x ${cols} -y ${rows} -c '${cwd}' '${shellCmd}'`, { env });
221
141
  console.log(`[relay] Created tmux session: ${tmuxName}`);
222
142
  } else {
223
- resizeTmuxWindow(tmuxName, cols, rows);
143
+ try {
144
+ execSync(`tmux resize-window -t ${tmuxName} -x ${cols} -y ${rows} 2>/dev/null`);
145
+ } catch {}
224
146
  console.log(`[relay] Attaching to existing tmux session: ${tmuxName}`);
225
147
  }
226
148
  return pty.spawn("tmux", ["attach", "-t", tmuxName], {
@@ -242,7 +164,7 @@ function createSession(ws, msg) {
242
164
  if (agent === "shell") {
243
165
  agentBin = findShellBin();
244
166
  if (!agentBin) {
245
- const reason = "Shell not found. Set SHELL or install zsh/bash/sh.";
167
+ const reason = "No login shell found. Set SHELL or install zsh, bash, or sh.";
246
168
  console.error(`[relay] Session ${id} failed: ${reason}`);
247
169
  send(ws, { type: "session:error", error: reason });
248
170
  return null;
@@ -258,7 +180,7 @@ function createSession(ws, msg) {
258
180
  } else {
259
181
  agentBin = findClaudeBin();
260
182
  if (!agentBin) {
261
- const reason = "Claude CLI not found. Install it with: curl -fsSL https://claude.ai/install.sh | bash";
183
+ const reason = "Claude CLI not found. Install it with: npm install -g @anthropic-ai/claude-code";
262
184
  console.error(`[relay] Session ${id} failed: ${reason}`);
263
185
  send(ws, { type: "session:error", error: reason });
264
186
  return null;
@@ -282,7 +204,8 @@ function createSession(ws, msg) {
282
204
  }
283
205
  let agentArgs;
284
206
  if (agent === "shell") {
285
- agentArgs = [];
207
+ const shellName = agentBin.split("/").pop() ?? "";
208
+ agentArgs = shellName === "sh" ? [] : ["-l"];
286
209
  } else if (agent === "pi") {
287
210
  agentArgs = ["--verbose"];
288
211
  const provider = normalizePiProviderForCli(msg.provider);