diff --git a/packages/cli/src/commands/schedule/create.ts b/packages/cli/src/commands/schedule/create.ts index d1bc60ad2..957722b70 100644 --- a/packages/cli/src/commands/schedule/create.ts +++ b/packages/cli/src/commands/schedule/create.ts @@ -17,6 +17,7 @@ export interface ScheduleCreateOptions extends ScheduleCommandOptions { target?: string; provider?: string; mode?: string; + cwd?: string; maxRuns?: string; expiresIn?: string; } @@ -34,6 +35,8 @@ export async function runCreateCommand( target: options.target, provider: options.provider, mode: options.mode, + cwd: options.cwd, + host: options.host, maxRuns: options.maxRuns, expiresIn: options.expiresIn, }); diff --git a/packages/cli/src/commands/schedule/index.ts b/packages/cli/src/commands/schedule/index.ts index b4a05ccff..9caee32d8 100644 --- a/packages/cli/src/commands/schedule/index.ts +++ b/packages/cli/src/commands/schedule/index.ts @@ -29,6 +29,7 @@ export function createScheduleCommand(): Command { "--mode ", "Provider-specific mode (e.g. claude bypassPermissions, opencode build)", ) + .option("--cwd ", "Working directory (default: current; required with --host)") .option("--max-runs ", "Maximum number of runs") .option("--expires-in ", "Time to live for the schedule"), ).action(withOutput(runCreateCommand)); diff --git a/packages/cli/src/commands/schedule/shared.test.ts b/packages/cli/src/commands/schedule/shared.test.ts new file mode 100644 index 000000000..81daab83f --- /dev/null +++ b/packages/cli/src/commands/schedule/shared.test.ts @@ -0,0 +1,62 @@ +import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; + +import { parseScheduleCreateInput } from "./shared.js"; + +const baseOptions = { + prompt: "do the thing", + every: "5m", + provider: "claude", +}; + +describe("parseScheduleCreateInput cwd/host validation", () => { + beforeEach(() => { + vi.spyOn(process, "cwd").mockReturnValue("/local/project"); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + test("no host, no cwd → defaults to process.cwd()", () => { + const input = parseScheduleCreateInput(baseOptions); + expect(input.target).toEqual({ + type: "new-agent", + config: { provider: "claude", cwd: "/local/project" }, + }); + }); + + test("no host, with cwd → uses provided cwd", () => { + const input = parseScheduleCreateInput({ ...baseOptions, cwd: "/some/other/path" }); + expect(input.target).toEqual({ + type: "new-agent", + config: { provider: "claude", cwd: "/some/other/path" }, + }); + }); + + test("host with cwd → uses provided cwd", () => { + const input = parseScheduleCreateInput({ + ...baseOptions, + host: "dev:6767", + cwd: "/remote/project", + }); + expect(input.target).toEqual({ + type: "new-agent", + config: { provider: "claude", cwd: "/remote/project" }, + }); + }); + + test("host without cwd → throws MISSING_CWD", () => { + expect(() => parseScheduleCreateInput({ ...baseOptions, host: "dev:6767" })).toThrow( + expect.objectContaining({ + code: "MISSING_CWD", + message: expect.stringContaining("--cwd is required when --host is specified"), + }), + ); + }); + + test("host with whitespace-only cwd → throws MISSING_CWD", () => { + expect(() => + parseScheduleCreateInput({ ...baseOptions, host: "dev:6767", cwd: " " }), + ).toThrow(expect.objectContaining({ code: "MISSING_CWD" })); + }); +}); diff --git a/packages/cli/src/commands/schedule/shared.ts b/packages/cli/src/commands/schedule/shared.ts index b0c97d1f8..9e59d4944 100644 --- a/packages/cli/src/commands/schedule/shared.ts +++ b/packages/cli/src/commands/schedule/shared.ts @@ -131,6 +131,8 @@ export function parseScheduleCreateInput(options: { target?: string; provider?: string; mode?: string; + cwd?: string; + host?: string; maxRuns?: string; expiresIn?: string; }): CreateScheduleInput { @@ -154,6 +156,15 @@ export function parseScheduleCreateInput(options: { ? { type: "every", everyMs: parseDuration(options.every) } : { type: "cron", expression: options.cron!.trim() }; + const cwdInput = options.cwd?.trim(); + if (options.host !== undefined && !cwdInput) { + throw { + code: "MISSING_CWD", + message: + "--cwd is required when --host is specified (the local working directory will not exist on the remote daemon)", + } satisfies CommandError; + } + const targetValue = options.target?.trim(); const modeId = options.mode?.trim(); const hasExplicitNewAgentOption = options.provider !== undefined || options.mode !== undefined; @@ -165,7 +176,7 @@ export function parseScheduleCreateInput(options: { type: "new-agent", config: { provider: resolvedProviderModel.provider, - cwd: process.cwd(), + cwd: cwdInput ?? process.cwd(), ...(resolvedProviderModel.model ? { model: resolvedProviderModel.model } : {}), ...(modeId ? { modeId } : {}), },