Add cwd option for command replies

This commit is contained in:
Peter Steinberger
2025-11-25 16:19:13 +01:00
parent 1ef7f4dbad
commit bcbf0de240
5 changed files with 27 additions and 4 deletions

View File

@@ -2,7 +2,11 @@
## [Unreleased] 0.1.3
_Add notes here for the next release._
### Features
- Added `cwd` option to command reply config for setting the working directory where commands execute. Essential for Claude Code to have proper project context.
### Developer notes
- Command auto-replies now pass `{ timeoutMs, cwd }` into the command runner; custom runners/tests that stub `runCommandWithTimeout` should accept the options object as well as the legacy numeric timeout.
## 0.1.2 — 2025-11-25

View File

@@ -21,6 +21,8 @@ warelay reads `~/.warelay/warelay.json` (JSON5 accepted). Add a command-mode rep
allowFrom: ["+15551234567"],
reply: {
mode: "command",
// Working directory for command execution (useful for Claude Code project context).
cwd: "/Users/you/Projects/my-project",
// Prepended before the inbound body; good for system prompts.
bodyPrefix: "You are a concise WhatsApp assistant. Keep replies under 1500 characters.\n\n",
// Claude CLI argv; the final element is the prompt/body provided by warelay.
@@ -38,6 +40,7 @@ warelay reads `~/.warelay/warelay.json` (JSON5 accepted). Add a command-mode rep
```
Notes on this configuration:
- `cwd` sets the working directory where the command runs. This is essential for Claude Code to have the right project context—Claude will see the project's `CLAUDE.md`, have access to project files, and understand the codebase structure.
- warelay automatically injects a Claude identity prefix and the correct `--output-format`/`-p` flags when `command[0]` is `claude` and `claudeOutputFormat` is set.
- Sessions are stored in `~/.warelay/sessions.json`; `scope: per-sender` keeps separate threads for each contact.
- `bodyPrefix` is added before the inbound message body that reaches Claude. The string above mirrors the built-in 1500-character WhatsApp guardrail.

View File

@@ -288,11 +288,13 @@ export async function getReplyFromConfig(
[CLAUDE_IDENTITY_PREFIX, existingBody].filter(Boolean).join("\n\n"),
];
}
logVerbose(`Running command auto-reply: ${finalArgv.join(" ")}`);
logVerbose(
`Running command auto-reply: ${finalArgv.join(" ")}${reply.cwd ? ` (cwd: ${reply.cwd})` : ""}`,
);
const started = Date.now();
try {
const { stdout, stderr, code, signal, killed } = await enqueueCommand(
() => commandRunner(finalArgv, timeoutMs),
() => commandRunner(finalArgv, { timeoutMs, cwd: reply.cwd }),
{
onWait: (waitMs, queuedAhead) => {
if (isVerbose()) {

View File

@@ -26,6 +26,7 @@ export type WarelayConfig = {
mode: ReplyMode;
text?: string; // for mode=text, can contain {{Body}}
command?: string[]; // for mode=command, argv with templates
cwd?: string; // working directory for command execution
template?: string; // prepend template string when building command/prompt
timeoutSeconds?: number; // optional command timeout; defaults to 600s
bodyPrefix?: string; // optional string prepended to Body before templating
@@ -43,6 +44,7 @@ const ReplySchema = z
mode: z.union([z.literal("text"), z.literal("command")]),
text: z.string().optional(),
command: z.array(z.string()).optional(),
cwd: z.string().optional(),
template: z.string().optional(),
timeoutSeconds: z.number().int().positive().optional(),
bodyPrefix: z.string().optional(),

View File

@@ -43,14 +43,26 @@ export type SpawnResult = {
killed: boolean;
};
export type CommandOptions = {
timeoutMs: number;
cwd?: string;
};
export async function runCommandWithTimeout(
argv: string[],
timeoutMs: number,
optionsOrTimeout: number | CommandOptions,
): Promise<SpawnResult> {
const options: CommandOptions =
typeof optionsOrTimeout === "number"
? { timeoutMs: optionsOrTimeout }
: optionsOrTimeout;
const { timeoutMs, cwd } = options;
// Spawn with inherited stdin (TTY) so tools like `claude` don't hang.
return await new Promise((resolve, reject) => {
const child = spawn(argv[0], argv.slice(1), {
stdio: ["inherit", "pipe", "pipe"],
cwd,
});
let stdout = "";
let stderr = "";