/**
* `--cwd
` on an agent run is NOT an agent-yes flag.
*
* It once selected the directory the agent runs in. It no longer does: agent-yes
* forwards it to the target CLI like any other unknown option, so the agent runs
* wherever `ay` was invoked and the CLI decides what `--cwd` means (usually:
* rejects it). Because agent-yes never handles a cwd but its own process cwd, the
* wrapper cwd / agent cwd / recorded cwd cannot drift apart.
*
* The shell does this job better anyway: `cd && ` puts the
* agent AND every relative path in the command in the same place, with no
* agent-yes-specific flag to remember. This module detects the flag on a raw argv
* and builds the copy-pasteable hint we print before continuing.
*
* Scope: the agent-run path only (ts/cli.ts), and only tokens BEFORE `--` —
* past the separator the token belongs to the CLI or the prompt. Management
* subcommands that take a `--cwd` FILTER (`ay ls/status/spawn/schedule …`) are a
* different flag and are unaffected — they never reach this code because
* subcommands are dispatched earlier. The Rust runner mirrors this hint in
* rs/src/main.rs for direct `agent-yes` invocations that bypass this launcher.
*/
/** Env var the JS launcher sets on the spawned Rust child so the hint, once
* printed here, is not printed a second time by the Rust runner. Mirrored in
* rs/src/main.rs. */
export const SUPPRESS_CWD_WARN_ENV = "AGENT_YES_SUPPRESS_CWD_WARN";
export interface CwdPassthroughHint {
/** The directory value the user passed to --cwd (undefined if the flag had no value). */
dir: string | undefined;
/** The suggested replacement command line, e.g. `cd ~/foo && cy claude -- fix`. */
suggestion: string;
/** Colorized, multi-line warning ready to write to stderr (no trailing newline). */
message: string;
}
/**
* Quote a single argv token for display in a copy-pasteable shell command.
* Leaves shell-safe tokens (including a leading `~`/`~/path`, so `cd ~/foo`
* still expands) bare; single-quotes anything else.
*/
function shellDisplayQuote(s: string): string {
if (s === "") return "''";
// Safe unquoted: word chars and the handful of path/opt punctuation that carry
// no shell meaning here. `~` included so a home-relative dir keeps expanding.
if (/^[A-Za-z0-9_@%+=:,./~-]+$/.test(s)) return s;
return `'${s.replace(/'/g, `'\\''`)}'`;
}
/** Program name as the user typed it, derived from argv[1] (the launched script). */
function programName(scriptPath: string | undefined): string {
if (!scriptPath) return "agent-yes";
const base = scriptPath.split(/[\\/]/).pop() || scriptPath;
return base.replace(/\.(js|ts|mjs|cjs)$/, "");
}
/**
* Detect a `--cwd ` / `--cwd=` token on a full process.argv
* (`[exec, script, ...userArgs]`). Returns the hint, or null when no `--cwd`
* appears before the `--` separator.
*/
export function detectCwdPassthrough(argv: string[]): CwdPassthroughHint | null {
const prog = programName(argv[1]);
const userArgs = argv.slice(2);
// Past `--` the token is the CLI's or the prompt's — forwarded verbatim either
// way, so there is nothing to suggest and nothing to strip.
const sepIndex = userArgs.indexOf("--");
const optionEnd = sepIndex === -1 ? userArgs.length : sepIndex;
let sawCwd = false;
let dir: string | undefined;
const rest: string[] = [];
for (let i = 0; i < userArgs.length; i++) {
const arg = userArgs[i]!;
if (i < optionEnd && arg === "--cwd") {
sawCwd = true;
const next = userArgs[i + 1];
// `--cwd DIR` — consume the value; a following flag means the value is missing.
if (next !== undefined && !next.startsWith("-")) {
dir = next;
i++;
}
continue;
}
if (i < optionEnd && arg.startsWith("--cwd=")) {
sawCwd = true;
dir = arg.slice("--cwd=".length);
continue;
}
rest.push(arg);
}
if (!sawCwd) return null;
const cmd = [prog, ...rest].map(shellDisplayQuote).join(" ");
// `` is a placeholder shown when the flag had no value — keep it bare.
const dirDisplay = dir === undefined ? "" : shellDisplayQuote(dir);
const suggestion = `cd ${dirDisplay} && ${cmd}`;
const message =
`\x1b[33m⚠ --cwd is not an agent-yes flag\x1b[0m — it is passed straight through to the CLI. ` +
`To run the agent somewhere else:\n\n` +
` ${suggestion}`;
return { dir, suggestion, message };
}