Files
AgentHub/server/lib/shell-path.js
T
nntrivi2001 8fdda66893 fix: v2.5.9 — Vietnamese in the terminal on macOS, every slash command, the right Claude account
Issue #62: a tmux client whose LC_ALL/LC_CTYPE/LANG names no UTF-8 prints `_`
for every non-ASCII character, and a macOS LaunchAgent starts the dashboard
with no locale at all — Vietnamese text and Claude Code's logo came out as
underscores in every browser terminal.

- shell-path.js: ensureUtf8Locale() defaults LC_CTYPE (en_US.UTF-8 on macOS,
  C.UTF-8 elsewhere) at start, before any tmux call; an explicit LC_ALL and
  Windows are left alone
- RunSetup.tsx: the `/` list now offers Claude Code's bundled skills
  (/code-review, /security-review, /simplify, /loop…) and newer built-ins, and
  every enabled plugin's commands and skills as /<plugin>:<name>
- Claude login decided by the TOKEN's owner (GET /api/oauth/profile, cached
  per token), not by .claude.json: a running session can leave the
  credentials store holding one account's token under another account's
  name. The Profiles modal, its Check quota and the status-bar quota now show
  the account requests are really billed to and warn on the mismatch; a
  switch saves the outgoing token into its OWNER's profile (credentials only)
  instead of overwriting the profile .claude.json names; saving a profile
  while they disagree is refused (EMISMATCH); a saved profile holding another
  account's token reports EWRONGACCOUNT instead of that account's usage
- Running Claude Code sessions follow a switch on their own (they re-read the
  credentials store when it changes), so the "close your sessions first"
  warning is gone
2026-10-07 11:18:10 +07:00

78 lines
3.3 KiB
JavaScript

/**
* @file Merge the user's login-shell PATH into `process.env.PATH`. A systemd
* unit or LaunchAgent snapshots PATH when it is installed, so a CLI installed
* later into a new directory (nvm, ~/.local/bin, a vendor installer) is
* invisible to AI Tools detection and to every lane pane. `$SHELL -ilc` runs
* the rc files where those installers add to PATH; delimiters discard
* whatever the rc files print, stdin is closed so a prompt gets EOF, and a
* timeout bounds a hang. Off with `AGENTHUB_SHELL_PATH=0`. The shell's own
* PATH is also kept in ITS order (`shellPath()`): the merge appends, so an
* install job that must use the shell's npm puts this first. Adapted from Orca's
* `hydrate-shell-path.ts` (MIT). `ensureUtf8Locale` fills the other gap such
* a service leaves: no locale at all, which makes every `tmux attach` print
* `_` for each non-ASCII character.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
const { execFile } = require("node:child_process");
const path = require("node:path");
const MARK = "__AGENTHUB_PATH__";
let lastShellPath = null;
/** The PATH the login shell last printed, in its order; null before/without one. */
const shellPath = () => lastShellPath;
/**
* @returns {Promise<boolean>} whether a shell PATH was read and merged.
*/
function hydrateShellPath({
spawn = execFile,
shell = process.env.SHELL || "/bin/bash",
timeoutMs = 5000,
} = {}) {
if (/^(0|false|off)$/i.test(process.env.AGENTHUB_SHELL_PATH || "")) return Promise.resolve(false);
return new Promise((resolve) => {
let child;
try {
child = spawn(
shell,
["-ilc", `printf '${MARK}%s${MARK}' "$PATH"`],
{ timeout: timeoutMs, encoding: "utf8" },
(err, stdout) => {
const m = String(stdout || "").match(new RegExp(`${MARK}(.*?)${MARK}`, "s"));
if (!m) return resolve(false); // timeout, spawn error, no output
lastShellPath = m[1];
const have = (process.env.PATH || "").split(path.delimiter).filter(Boolean);
const add = m[1].split(path.delimiter).filter((d) => d && !have.includes(d));
process.env.PATH = [...have, ...new Set(add)].join(path.delimiter);
resolve(true);
}
);
} catch {
return resolve(false);
}
// An rc file that reads stdin gets EOF instead of hanging until the timeout.
child?.stdin?.end();
});
}
/**
* A LaunchAgent (and some service managers) start the dashboard with no
* LC_ALL/LC_CTYPE/LANG, and a tmux client whose first set one does not name
* UTF-8 replaces every non-ASCII character with `_` — Vietnamese text and
* Claude Code's block-art logo in every browser terminal. Default LC_CTYPE
* (charset only, no message/date changes) so the attach clients, and the
* panes of a tmux server started from here, inherit it. An explicit LC_ALL is
* the user's choice and wins; Windows (psmux) is left alone. macOS has no
* C.UTF-8, and glibc systems may not have en_US.UTF-8 generated.
*/
function ensureUtf8Locale(env = process.env, platform = process.platform) {
if (platform === "win32" || env.LC_ALL) return;
if (/UTF-?8/i.test(env.LC_CTYPE || env.LANG || "")) return;
env.LC_CTYPE = platform === "darwin" ? "en_US.UTF-8" : "C.UTF-8";
}
module.exports = { hydrateShellPath, shellPath, ensureUtf8Locale };