clay-server 2.33.1 → 2.34.0-beta.2

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.
@@ -20,6 +20,14 @@ var todoItems = [];
20
20
  var todoWidgetEl = null;
21
21
  var todoWidgetVisible = true; // whether in-chat widget is in viewport
22
22
  var todoObserver = null;
23
+ var todoMeta = {
24
+ variant: "tasks",
25
+ title: "Tasks",
26
+ icon: "list-checks",
27
+ showProgress: true,
28
+ showCompletedCount: true,
29
+ stickyEnabled: true,
30
+ };
23
31
 
24
32
  // --- Tool tracking ---
25
33
  var tools = {};
@@ -1262,6 +1270,7 @@ function todoStatusIcon(status) {
1262
1270
 
1263
1271
  export function handleTodoWrite(input) {
1264
1272
  if (!input || !Array.isArray(input.todos)) return;
1273
+ todoMeta = normalizeTodoMeta(input.meta);
1265
1274
  todoItems = input.todos.map(function (t, i) {
1266
1275
  return {
1267
1276
  id: t.id || String(i + 1),
@@ -1275,6 +1284,7 @@ export function handleTodoWrite(input) {
1275
1284
 
1276
1285
  export function handleTaskCreate(input) {
1277
1286
  if (!input) return;
1287
+ todoMeta = normalizeTodoMeta();
1278
1288
  var id = String(todoItems.length + 1);
1279
1289
  todoItems.push({
1280
1290
  id: id,
@@ -1287,6 +1297,7 @@ export function handleTaskCreate(input) {
1287
1297
 
1288
1298
  export function handleTaskUpdate(input) {
1289
1299
  if (!input || !input.taskId) return;
1300
+ todoMeta = normalizeTodoMeta();
1290
1301
  for (var i = 0; i < todoItems.length; i++) {
1291
1302
  if (todoItems[i].id === input.taskId) {
1292
1303
  if (input.status === "deleted") {
@@ -1302,11 +1313,33 @@ export function handleTaskUpdate(input) {
1302
1313
  renderTodoWidget();
1303
1314
  }
1304
1315
 
1316
+ function normalizeTodoMeta(meta) {
1317
+ if (meta && meta.variant === "plan") {
1318
+ return {
1319
+ variant: "plan",
1320
+ title: "Plan",
1321
+ icon: "map",
1322
+ showProgress: false,
1323
+ showCompletedCount: false,
1324
+ stickyEnabled: false,
1325
+ };
1326
+ }
1327
+ return {
1328
+ variant: "tasks",
1329
+ title: "Tasks",
1330
+ icon: "list-checks",
1331
+ showProgress: true,
1332
+ showCompletedCount: true,
1333
+ stickyEnabled: true,
1334
+ };
1335
+ }
1336
+
1305
1337
  function renderTodoWidget() {
1306
1338
  if (todoItems.length === 0) {
1307
1339
  if (todoWidgetEl) { todoWidgetEl.remove(); todoWidgetEl = null; }
1308
1340
  if (todoObserver) { todoObserver.disconnect(); todoObserver = null; }
1309
1341
  todoWidgetVisible = true;
1342
+ todoMeta = normalizeTodoMeta();
1310
1343
  updateTodoSticky();
1311
1344
  return;
1312
1345
  }
@@ -1316,19 +1349,26 @@ function renderTodoWidget() {
1316
1349
  todoWidgetEl = document.createElement("div");
1317
1350
  todoWidgetEl.className = "todo-widget";
1318
1351
  }
1352
+ todoWidgetEl.className = "todo-widget" + (todoMeta.variant === "plan" ? " todo-widget-plan" : "");
1319
1353
 
1320
1354
  var completed = 0;
1321
1355
  for (var i = 0; i < todoItems.length; i++) {
1322
1356
  if (todoItems[i].status === "completed") completed++;
1323
1357
  }
1324
1358
 
1359
+ var countText = todoMeta.showCompletedCount
1360
+ ? (completed + "/" + todoItems.length)
1361
+ : (todoItems.length + " " + (todoItems.length === 1 ? "step" : "steps"));
1362
+
1325
1363
  var html = '<div class="todo-header">' +
1326
- '<span class="todo-header-icon">' + iconHtml("list-checks") + '</span>' +
1327
- '<span class="todo-header-title">Tasks</span>' +
1328
- '<span class="todo-header-count">' + completed + '/' + todoItems.length + '</span>' +
1364
+ '<span class="todo-header-icon">' + iconHtml(todoMeta.icon) + '</span>' +
1365
+ '<span class="todo-header-title">' + todoMeta.title + '</span>' +
1366
+ '<span class="todo-header-count">' + countText + '</span>' +
1329
1367
  '</div>';
1330
- html += '<div class="todo-progress"><div class="todo-progress-bar" style="width:' +
1331
- (todoItems.length > 0 ? Math.round(completed / todoItems.length * 100) : 0) + '%"></div></div>';
1368
+ if (todoMeta.showProgress) {
1369
+ html += '<div class="todo-progress"><div class="todo-progress-bar" style="width:' +
1370
+ (todoItems.length > 0 ? Math.round(completed / todoItems.length * 100) : 0) + '%"></div></div>';
1371
+ }
1332
1372
  html += '<div class="todo-items">';
1333
1373
  for (var i = 0; i < todoItems.length; i++) {
1334
1374
  var t = todoItems[i];
@@ -1369,6 +1409,10 @@ function setupTodoObserver() {
1369
1409
  function updateTodoStickyVisibility() {
1370
1410
  var stickyEl = document.getElementById("todo-sticky");
1371
1411
  if (!stickyEl) return;
1412
+ if (!todoMeta.stickyEnabled) {
1413
+ stickyEl.classList.add("hidden");
1414
+ return;
1415
+ }
1372
1416
 
1373
1417
  if (todoWidgetVisible) {
1374
1418
  stickyEl.classList.add("hidden");
@@ -1387,6 +1431,11 @@ function updateTodoStickyVisibility() {
1387
1431
  function updateTodoSticky() {
1388
1432
  var stickyEl = document.getElementById("todo-sticky");
1389
1433
  if (!stickyEl) return;
1434
+ if (!todoMeta.stickyEnabled) {
1435
+ stickyEl.classList.add("hidden");
1436
+ stickyEl.innerHTML = "";
1437
+ return;
1438
+ }
1390
1439
 
1391
1440
  // Hide if no active tasks (all completed or empty)
1392
1441
  var hasActive = false;
@@ -2195,6 +2244,7 @@ export function saveToolState() {
2195
2244
  tools: tools,
2196
2245
  currentThinking: currentThinking,
2197
2246
  todoWidgetEl: todoWidgetEl,
2247
+ todoMeta: todoMeta,
2198
2248
  inPlanMode: inPlanMode,
2199
2249
  planContent: planContent,
2200
2250
  currentPlanCardEl: currentPlanCardEl,
@@ -2209,6 +2259,7 @@ export function restoreToolState(saved) {
2209
2259
  tools = saved.tools;
2210
2260
  currentThinking = saved.currentThinking;
2211
2261
  todoWidgetEl = saved.todoWidgetEl;
2262
+ todoMeta = saved.todoMeta || normalizeTodoMeta();
2212
2263
  inPlanMode = saved.inPlanMode;
2213
2264
  planContent = saved.planContent;
2214
2265
  currentPlanCardEl = saved.currentPlanCardEl || null;
@@ -2229,6 +2280,7 @@ export function resetToolState() {
2229
2280
  planContent = null;
2230
2281
  currentPlanCardEl = null;
2231
2282
  todoItems = [];
2283
+ todoMeta = normalizeTodoMeta();
2232
2284
  todoWidgetEl = null;
2233
2285
  todoWidgetVisible = true;
2234
2286
  if (todoObserver) { todoObserver.disconnect(); todoObserver = null; }
package/lib/sdk-bridge.js CHANGED
@@ -1,7 +1,8 @@
1
1
  const crypto = require("crypto");
2
2
  var fs = require("fs");
3
3
  var path = require("path");
4
- var { execSync } = require("child_process");
4
+ var execSync = require("child_process").execSync;
5
+ var execFileSync = require("child_process").execFileSync;
5
6
  var usersModule = require("./users");
6
7
  var { getCodexConfig } = require("./codex-defaults");
7
8
  var { splitShellSegments, attachSkillDiscovery } = require("./sdk-skill-discovery");
@@ -373,12 +374,12 @@ function createSDKBridge(opts) {
373
374
  // Create and chown the project directory once
374
375
  if (!fs.existsSync(dstDir)) {
375
376
  fs.mkdirSync(dstDir, { recursive: true });
376
- try { require("child_process").execSync("chown -R " + uid + " " + JSON.stringify(path.join(linuxUserHome, ".claude"))); } catch (e2) {}
377
+ try { execFileSync("chown", ["-R", String(uid), path.join(linuxUserHome, ".claude")]); } catch (e2) {}
377
378
  } else {
378
379
  try {
379
380
  var dirStat = fs.statSync(dstDir);
380
381
  if (dirStat.uid !== uid) {
381
- require("child_process").execSync("chown " + uid + " " + JSON.stringify(dstDir));
382
+ execFileSync("chown", [String(uid), dstDir]);
382
383
  }
383
384
  } catch (e2) {}
384
385
  }
@@ -389,7 +390,7 @@ function createSDKBridge(opts) {
389
390
  var dstFile = path.join(dstDir, sessionFileName);
390
391
  if (fs.existsSync(srcFile) && !fs.existsSync(dstFile)) {
391
392
  fs.copyFileSync(srcFile, dstFile);
392
- try { require("child_process").execSync("chown " + uid + " " + JSON.stringify(dstFile)); } catch (e2) {}
393
+ try { execFileSync("chown", [String(uid), dstFile]); } catch (e2) {}
393
394
  console.log("[sdk-bridge] Pre-copied CLI session " + session.cliSessionId + " to " + linuxUser);
394
395
  }
395
396
  }
@@ -447,6 +448,12 @@ function createSDKBridge(opts) {
447
448
  }
448
449
  }
449
450
 
451
+ // Auto-approve Mate datastore tools. These are scoped to the active Mate
452
+ // project and already enforce SQL policy server-side.
453
+ if (toolName.indexOf("mcp__clay-datastore__") === 0) {
454
+ return { behavior: "allow", updatedInput: input };
455
+ }
456
+
450
457
  // Auto-approve remote MCP tools that the user explicitly enabled in project settings.
451
458
  // These are user-owned local MCP servers, so no additional permission prompt needed.
452
459
  if (toolName.indexOf("mcp__") === 0 && getRemoteMcpServers) {
@@ -646,7 +653,7 @@ function createSDKBridge(opts) {
646
653
  */
647
654
  function findConflictingClaude() {
648
655
  try {
649
- var output = execSync("ps ax -o pid,command 2>/dev/null", { encoding: "utf8", timeout: 5000 });
656
+ var output = execFileSync("ps", ["ax", "-o", "pid,command"], { encoding: "utf8", timeout: 5000, stdio: ["pipe", "pipe", "pipe"] });
650
657
  var lines = output.trim().split("\n");
651
658
  var candidates = [];
652
659
  for (var i = 1; i < lines.length; i++) { // skip header
@@ -691,7 +698,7 @@ function createSDKBridge(opts) {
691
698
  */
692
699
  function isClaudeProcess(pid) {
693
700
  try {
694
- var output = execSync("ps -p " + pid + " -o command= 2>/dev/null", { encoding: "utf8", timeout: 3000 }).trim();
701
+ var output = execFileSync("ps", ["-p", String(pid), "-o", "command="], { encoding: "utf8", timeout: 3000, stdio: ["pipe", "pipe", "pipe"] }).trim();
695
702
  return /\/claude(\s|$)/.test(output) || /^claude(\s|$)/.test(output);
696
703
  } catch (e) {
697
704
  return false;
@@ -1168,16 +1175,18 @@ function createSDKBridge(opts) {
1168
1175
 
1169
1176
  if (dangerouslySkipPermissions) {
1170
1177
  claudeOpts.allowDangerouslySkipPermissions = true;
1171
- }
1172
- var globalMode = sm.currentPermissionMode || "default";
1173
- var effectiveDefault;
1174
- if (globalMode === "bypassPermissions") effectiveDefault = "bypassPermissions";
1175
- else if (session.acceptEditsAfterStart) effectiveDefault = "acceptEdits";
1176
- else effectiveDefault = globalMode;
1177
- var modeToApply = session._loopPermissionMode || effectiveDefault;
1178
- if (session.acceptEditsAfterStart) delete session.acceptEditsAfterStart;
1179
- if (modeToApply && modeToApply !== "default") {
1180
- claudeOpts.permissionMode = modeToApply;
1178
+ claudeOpts.permissionMode = "bypassPermissions";
1179
+ } else {
1180
+ var globalMode = sm.currentPermissionMode || "default";
1181
+ var effectiveDefault;
1182
+ if (globalMode === "bypassPermissions") effectiveDefault = "bypassPermissions";
1183
+ else if (session.acceptEditsAfterStart) effectiveDefault = "acceptEdits";
1184
+ else effectiveDefault = globalMode;
1185
+ var modeToApply = session._loopPermissionMode || effectiveDefault;
1186
+ if (session.acceptEditsAfterStart) delete session.acceptEditsAfterStart;
1187
+ if (modeToApply && modeToApply !== "default") {
1188
+ claudeOpts.permissionMode = modeToApply;
1189
+ }
1181
1190
  }
1182
1191
  if (session.cliSessionId && session.lastRewindUuid) {
1183
1192
  claudeOpts.resumeSessionAt = session.lastRewindUuid;
@@ -1349,17 +1358,17 @@ function createSDKBridge(opts) {
1349
1358
  // Detect which vendor binaries are installed for this user.
1350
1359
  // In multi-user mode, runs checks as the specific Linux user.
1351
1360
  function detectInstalledVendors(linuxUser) {
1352
- var execSync = require("child_process").execSync;
1361
+ var execFileSync = require("child_process").execFileSync;
1353
1362
  var fs = require("fs");
1354
- var path = require("path");
1355
1363
  var result = [];
1356
1364
 
1357
- function tryExec(cmd) {
1365
+ function tryLookup(name) {
1358
1366
  try {
1359
1367
  if (linuxUser) {
1360
- execSync("su - " + linuxUser + " -c " + JSON.stringify(cmd), { timeout: 3000, stdio: ["pipe", "pipe", "pipe"] });
1368
+ execFileSync("su", ["-", linuxUser, "-c", "which " + name], { timeout: 3000, stdio: ["pipe", "pipe", "pipe"] });
1361
1369
  } else {
1362
- execSync(cmd, { timeout: 3000, stdio: ["pipe", "pipe", "pipe"] });
1370
+ if (process.platform === "win32") execFileSync("where", [name], { timeout: 3000, stdio: ["pipe", "pipe", "pipe"] });
1371
+ else execFileSync("which", [name], { timeout: 3000, stdio: ["pipe", "pipe", "pipe"] });
1363
1372
  }
1364
1373
  return true;
1365
1374
  } catch (e) {
@@ -1368,14 +1377,14 @@ function createSDKBridge(opts) {
1368
1377
  }
1369
1378
 
1370
1379
  // Claude: check if binary is in PATH
1371
- if (tryExec("which claude")) result.push("claude");
1380
+ if (tryLookup("claude")) result.push("claude");
1372
1381
 
1373
1382
  // Codex: check bundled binary or PATH
1374
1383
  var codexBin = null;
1375
1384
  try {
1376
1385
  codexBin = require("./yoke/codex-app-server").findCodexPath();
1377
1386
  } catch (e) {}
1378
- if ((codexBin && fs.existsSync(codexBin)) || tryExec("which codex")) result.push("codex");
1387
+ if ((codexBin && fs.existsSync(codexBin)) || tryLookup("codex")) result.push("codex");
1379
1388
 
1380
1389
  return result;
1381
1390
  }
@@ -234,7 +234,13 @@ function attachMessageProcessor(ctx) {
234
234
  type: "tool_executing",
235
235
  id: parsed.turnId || "codex-plan",
236
236
  name: "TodoWrite",
237
- input: { todos: todos },
237
+ input: {
238
+ todos: todos,
239
+ meta: {
240
+ variant: "plan",
241
+ title: parsed.title || "Plan",
242
+ },
243
+ },
238
244
  });
239
245
 
240
246
  } else if (parsed.yokeType === "plan_content") {
package/lib/updater.js CHANGED
@@ -1,5 +1,5 @@
1
1
  const https = require("https");
2
- const { execSync, spawn } = require("child_process");
2
+ const { execFileSync, spawn } = require("child_process");
3
3
 
4
4
  // ANSI helpers (mirrors cli.js)
5
5
  var isBasicTerm = process.env.TERM_PROGRAM === "Apple_Terminal";
@@ -84,7 +84,7 @@ function isNewer(latest, current) {
84
84
  function performUpdate(channel) {
85
85
  var tag = channel === "beta" ? "beta" : "latest";
86
86
  try {
87
- execSync("npm install -g clay-server@" + tag, { stdio: "pipe" });
87
+ execFileSync("npm", ["install", "-g", "clay-server@" + tag], { stdio: "pipe" });
88
88
  return true;
89
89
  } catch (e) {
90
90
  return false;
package/lib/users.js CHANGED
@@ -1,7 +1,7 @@
1
1
  var fs = require("fs");
2
2
  var path = require("path");
3
3
  var crypto = require("crypto");
4
- var { execSync } = require("child_process");
4
+ var execFileSync = require("child_process").execFileSync;
5
5
  var { CONFIG_DIR } = require("./config");
6
6
  var { attachAuth } = require("./users-auth");
7
7
  var { DEFAULT_PERMISSIONS, ALL_PERMISSIONS, attachPermissions } = require("./users-permissions");
@@ -243,7 +243,7 @@ function updateLinuxUser(userId, linuxUsername) {
243
243
 
244
244
  // Validate Linux user exists
245
245
  try {
246
- execSync("id " + linuxUsername, { encoding: "utf8", timeout: 5000 });
246
+ execFileSync("id", [linuxUsername], { encoding: "utf8", timeout: 5000, stdio: "pipe" });
247
247
  } catch (e) {
248
248
  return { error: "Linux user '" + linuxUsername + "' does not exist" };
249
249
  }
package/lib/ws-schema.js CHANGED
@@ -454,6 +454,20 @@ var schema = {
454
454
  "memory_delete": { direction: "c2s", handler: "lib/project.js", description: "Delete a memory entry by index" },
455
455
  "memory_deleted": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Memory entry was deleted" },
456
456
 
457
+ // -----------------------------------------------------------------------
458
+ // Mate datastore
459
+ // -----------------------------------------------------------------------
460
+ "mate_db_tables": { direction: "c2s", handler: "lib/project-mate-datastore.js", description: "List schema objects in the current Mate datastore" },
461
+ "mate_db_describe": { direction: "c2s", handler: "lib/project-mate-datastore.js", description: "Describe a table or view in the current Mate datastore" },
462
+ "mate_db_query": { direction: "c2s", handler: "lib/project-mate-datastore.js", description: "Run read-only SQL against the current Mate datastore" },
463
+ "mate_db_exec": { direction: "c2s", handler: "lib/project-mate-datastore.js", description: "Run schema or write SQL against the current Mate datastore" },
464
+ "mate_db_tables_result": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Schema objects returned from a Mate datastore" },
465
+ "mate_db_describe_result": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Table description returned from a Mate datastore" },
466
+ "mate_db_query_result": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Query results from a Mate datastore" },
467
+ "mate_db_exec_result": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Execution summary from a Mate datastore" },
468
+ "mate_db_error": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Mate datastore error" },
469
+ "mate_db_change": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Mate datastore changed" },
470
+
457
471
  // -----------------------------------------------------------------------
458
472
  // Loop (automated task runner)
459
473
  // -----------------------------------------------------------------------
@@ -943,6 +943,7 @@ function createClaudeAdapter(opts) {
943
943
  cwd: (initOpts && initOpts.cwd) || _cwd,
944
944
  settingSources: ["user", "project", "local"],
945
945
  abortController: ac,
946
+ settings: { disableAllHooks: true },
946
947
  };
947
948
  if (_claudeBinaryPath) warmupOptions.pathToClaudeCodeExecutable = _claudeBinaryPath;
948
949
 
@@ -1102,6 +1103,7 @@ function createClaudeAdapter(opts) {
1102
1103
  if (co.permissionMode) sdkOptions.permissionMode = co.permissionMode;
1103
1104
  if (co.allowDangerouslySkipPermissions) sdkOptions.allowDangerouslySkipPermissions = true;
1104
1105
  if (co.resumeSessionAt) sdkOptions.resumeSessionAt = co.resumeSessionAt;
1106
+ if (co.settings) sdkOptions.settings = co.settings;
1105
1107
 
1106
1108
  var rawQuery = sdk.query({ prompt: mq, options: sdkOptions });
1107
1109
  return createQueryHandle(rawQuery, mq, ac);
@@ -1250,6 +1252,7 @@ function createClaudeAdapter(opts) {
1250
1252
  if (claudeOpts.betas && claudeOpts.betas.length > 0) queryOptions.betas = claudeOpts.betas;
1251
1253
  if (claudeOpts.permissionMode) queryOptions.permissionMode = claudeOpts.permissionMode;
1252
1254
  if (claudeOpts.allowDangerouslySkipPermissions) queryOptions.allowDangerouslySkipPermissions = true;
1255
+ if (claudeOpts.settings) queryOptions.settings = claudeOpts.settings;
1253
1256
 
1254
1257
  if (queryOpts.toolServers) queryOptions.mcpServers = queryOpts.toolServers;
1255
1258
  if (queryOpts.model) queryOptions.model = queryOpts.model;
@@ -1356,6 +1359,7 @@ function createClaudeAdapter(opts) {
1356
1359
  cwd: (initOpts && initOpts.cwd) || _cwd,
1357
1360
  settingSources: ["user", "project", "local"],
1358
1361
  abortController: ac,
1362
+ settings: { disableAllHooks: true },
1359
1363
  };
1360
1364
  if (_claudeBinaryPath) warmupOptions.pathToClaudeCodeExecutable = _claudeBinaryPath;
1361
1365
 
@@ -1430,7 +1434,7 @@ function createClaudeAdapter(opts) {
1430
1434
  throw new Error("Warmup worker failed to connect: " + (e.message || e));
1431
1435
  }
1432
1436
 
1433
- var warmupOptions = { cwd: workerCwd, settingSources: ["user", "project", "local"] };
1437
+ var warmupOptions = { cwd: workerCwd, settingSources: ["user", "project", "local"], settings: { disableAllHooks: true } };
1434
1438
  if (_claudeBinaryPath) warmupOptions.pathToClaudeCodeExecutable = _claudeBinaryPath;
1435
1439
  if (initOpts && initOpts.dangerouslySkipPermissions) {
1436
1440
  warmupOptions.permissionMode = "bypassPermissions";
@@ -164,6 +164,7 @@ function flattenEvent(notification, state) {
164
164
  yokeType: "plan_updated",
165
165
  turnId: params.turnId || null,
166
166
  explanation: params.explanation || "",
167
+ title: "Plan",
167
168
  plan: Array.isArray(params.plan) ? params.plan.map(function(step) {
168
169
  return {
169
170
  step: step && step.step ? step.step : "",
package/lib/yoke/index.js CHANGED
@@ -82,6 +82,18 @@ function checkAuth() {
82
82
  if (_authCache) return _authCache;
83
83
 
84
84
  var execSync = require("child_process").execSync;
85
+ var execFileSync = require("child_process").execFileSync;
86
+
87
+ function lookupBinary(name) {
88
+ try {
89
+ if (process.platform === "win32") {
90
+ return execFileSync("where", [name], { timeout: 3000, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim().split(/\r?\n/)[0] || null;
91
+ }
92
+ return execFileSync("which", [name], { timeout: 3000, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim().split(/\r?\n/)[0] || null;
93
+ } catch (e) {
94
+ return null;
95
+ }
96
+ }
85
97
 
86
98
  function parseClaudeAuthStatusJson(out) {
87
99
  if (!out) return null;
@@ -127,27 +139,20 @@ function checkAuth() {
127
139
  function resolveCodexBinary() {
128
140
  var fs = require("fs");
129
141
  var findCodexPath = require("./codex-app-server").findCodexPath;
130
- var pathProbeCmd = process.platform === "win32" ? "where codex" : "which codex";
131
142
 
132
143
  try {
133
144
  var codexBin = findCodexPath();
134
145
  if (codexBin && fs.existsSync(codexBin)) return codexBin;
135
146
  } catch (e) {}
136
147
 
137
- try {
138
- var whichOut = execSync(pathProbeCmd, { timeout: 3000, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
139
- var codexPath = whichOut.trim().split(/\r?\n/)[0];
140
- if (codexPath) return codexPath;
141
- } catch (e) {}
142
-
143
- return null;
148
+ return lookupBinary("codex");
144
149
  }
145
150
 
146
151
  function checkCodex() {
147
152
  try {
148
153
  var codexBin = resolveCodexBinary();
149
154
  if (!codexBin) return false;
150
- execSync('"' + codexBin + '" login status', { timeout: 5000, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
155
+ execFileSync(codexBin, ["login", "status"], { timeout: 5000, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
151
156
  return true;
152
157
  } catch (e) {
153
158
  return false;
@@ -164,10 +169,11 @@ function checkAuth() {
164
169
  */
165
170
  function checkInstalled() {
166
171
  var fs = require("fs");
167
- var execSync = require("child_process").execSync;
172
+ var execFileSync = require("child_process").execFileSync;
168
173
  var result = { claude: false, codex: false };
169
174
  try {
170
- execSync("which claude", { timeout: 3000, stdio: ["pipe", "pipe", "pipe"] });
175
+ if (process.platform === "win32") execFileSync("where", ["claude"], { timeout: 3000, stdio: ["pipe", "pipe", "pipe"] });
176
+ else execFileSync("which", ["claude"], { timeout: 3000, stdio: ["pipe", "pipe", "pipe"] });
171
177
  result.claude = true;
172
178
  } catch (e) {}
173
179
  try {
@@ -181,8 +187,9 @@ function checkInstalled() {
181
187
  }
182
188
  } catch (e) {}
183
189
 
184
- var pathProbeCmd = process.platform === "win32" ? "where codex" : "which codex";
185
- var whichOut = execSync(pathProbeCmd, { timeout: 3000, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
190
+ var whichOut = process.platform === "win32"
191
+ ? execFileSync("where", ["codex"], { timeout: 3000, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] })
192
+ : execFileSync("which", ["codex"], { timeout: 3000, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
186
193
  if (whichOut.trim()) result.codex = true;
187
194
  } catch (e) {}
188
195
  return result;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "2.33.1",
3
+ "version": "2.34.0-beta.2",
4
4
  "description": "Self-hosted Claude Code in your browser. Multi-session, multi-user, push notifications.",
5
5
  "bin": {
6
6
  "clay-server": "./bin/cli.js",