Add cwd option for command replies
This commit is contained in:
@@ -2,7 +2,11 @@
|
|||||||
|
|
||||||
## [Unreleased] 0.1.3
|
## [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
|
## 0.1.2 — 2025-11-25
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ warelay reads `~/.warelay/warelay.json` (JSON5 accepted). Add a command-mode rep
|
|||||||
allowFrom: ["+15551234567"],
|
allowFrom: ["+15551234567"],
|
||||||
reply: {
|
reply: {
|
||||||
mode: "command",
|
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.
|
// Prepended before the inbound body; good for system prompts.
|
||||||
bodyPrefix: "You are a concise WhatsApp assistant. Keep replies under 1500 characters.\n\n",
|
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.
|
// 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:
|
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.
|
- 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.
|
- 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.
|
- `bodyPrefix` is added before the inbound message body that reaches Claude. The string above mirrors the built-in 1500-character WhatsApp guardrail.
|
||||||
|
|||||||
@@ -288,11 +288,13 @@ export async function getReplyFromConfig(
|
|||||||
[CLAUDE_IDENTITY_PREFIX, existingBody].filter(Boolean).join("\n\n"),
|
[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();
|
const started = Date.now();
|
||||||
try {
|
try {
|
||||||
const { stdout, stderr, code, signal, killed } = await enqueueCommand(
|
const { stdout, stderr, code, signal, killed } = await enqueueCommand(
|
||||||
() => commandRunner(finalArgv, timeoutMs),
|
() => commandRunner(finalArgv, { timeoutMs, cwd: reply.cwd }),
|
||||||
{
|
{
|
||||||
onWait: (waitMs, queuedAhead) => {
|
onWait: (waitMs, queuedAhead) => {
|
||||||
if (isVerbose()) {
|
if (isVerbose()) {
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export type WarelayConfig = {
|
|||||||
mode: ReplyMode;
|
mode: ReplyMode;
|
||||||
text?: string; // for mode=text, can contain {{Body}}
|
text?: string; // for mode=text, can contain {{Body}}
|
||||||
command?: string[]; // for mode=command, argv with templates
|
command?: string[]; // for mode=command, argv with templates
|
||||||
|
cwd?: string; // working directory for command execution
|
||||||
template?: string; // prepend template string when building command/prompt
|
template?: string; // prepend template string when building command/prompt
|
||||||
timeoutSeconds?: number; // optional command timeout; defaults to 600s
|
timeoutSeconds?: number; // optional command timeout; defaults to 600s
|
||||||
bodyPrefix?: string; // optional string prepended to Body before templating
|
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")]),
|
mode: z.union([z.literal("text"), z.literal("command")]),
|
||||||
text: z.string().optional(),
|
text: z.string().optional(),
|
||||||
command: z.array(z.string()).optional(),
|
command: z.array(z.string()).optional(),
|
||||||
|
cwd: z.string().optional(),
|
||||||
template: z.string().optional(),
|
template: z.string().optional(),
|
||||||
timeoutSeconds: z.number().int().positive().optional(),
|
timeoutSeconds: z.number().int().positive().optional(),
|
||||||
bodyPrefix: z.string().optional(),
|
bodyPrefix: z.string().optional(),
|
||||||
|
|||||||
@@ -43,14 +43,26 @@ export type SpawnResult = {
|
|||||||
killed: boolean;
|
killed: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type CommandOptions = {
|
||||||
|
timeoutMs: number;
|
||||||
|
cwd?: string;
|
||||||
|
};
|
||||||
|
|
||||||
export async function runCommandWithTimeout(
|
export async function runCommandWithTimeout(
|
||||||
argv: string[],
|
argv: string[],
|
||||||
timeoutMs: number,
|
optionsOrTimeout: number | CommandOptions,
|
||||||
): Promise<SpawnResult> {
|
): 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.
|
// Spawn with inherited stdin (TTY) so tools like `claude` don't hang.
|
||||||
return await new Promise((resolve, reject) => {
|
return await new Promise((resolve, reject) => {
|
||||||
const child = spawn(argv[0], argv.slice(1), {
|
const child = spawn(argv[0], argv.slice(1), {
|
||||||
stdio: ["inherit", "pipe", "pipe"],
|
stdio: ["inherit", "pipe", "pipe"],
|
||||||
|
cwd,
|
||||||
});
|
});
|
||||||
let stdout = "";
|
let stdout = "";
|
||||||
let stderr = "";
|
let stderr = "";
|
||||||
|
|||||||
Reference in New Issue
Block a user