diff --git a/packages/cli/src/commands/worktree/archive.ts b/packages/cli/src/commands/worktree/archive.ts index eaf3857322..5d2bc60f33 100644 --- a/packages/cli/src/commands/worktree/archive.ts +++ b/packages/cli/src/commands/worktree/archive.ts @@ -2,6 +2,7 @@ import path from "path"; import type { Command } from "commander"; import type { DaemonClient } from "@getpaseo/client/internal/daemon-client"; import { connectToDaemon, getDaemonHost } from "../../utils/client.js"; +import { resolveRepositoryDirectory } from "./repository-directory.js"; import type { CommandOptions, SingleResult, @@ -31,6 +32,7 @@ export const archiveSchema: OutputSchema = { export interface WorktreeArchiveOptions extends CommandOptions { host?: string; + cwd?: string; } export type WorktreeArchiveCommandResult = SingleResult; @@ -49,6 +51,7 @@ export async function runArchiveCommandWithDeps( deps: { connectToDaemon: typeof connectToDaemon }, ): Promise { const host = getDaemonHost({ target: options.daemonTarget }); + const cwd = resolveRepositoryDirectory(options); // Validate arguments if (!nameArg || nameArg.trim().length === 0) { @@ -76,7 +79,7 @@ export async function runArchiveCommandWithDeps( try { // Get the list of worktrees first to resolve the name - const listResponse = await client.getPaseoWorktreeList({}); + const listResponse = await client.getPaseoWorktreeList({ cwd }); if (listResponse.error) { const error: CommandError = { diff --git a/packages/cli/src/commands/worktree/index.ts b/packages/cli/src/commands/worktree/index.ts index f35f001a38..37405e2630 100644 --- a/packages/cli/src/commands/worktree/index.ts +++ b/packages/cli/src/commands/worktree/index.ts @@ -9,7 +9,10 @@ export function createWorktreeCommand(): Command { const worktree = new Command("worktree").description("Manage Paseo-managed git worktrees"); addJsonAndDaemonHostOptions( - worktree.command("ls").description("List Paseo-managed git worktrees"), + worktree + .command("ls") + .description("List Paseo-managed git worktrees") + .option("--cwd ", "Repository directory (default: current)"), ).action(withOutput(runLsCommand)); addJsonAndDaemonHostOptions( @@ -31,7 +34,8 @@ export function createWorktreeCommand(): Command { worktree .command("archive") .description("Archive a worktree (removes worktree and associated branch)") - .argument("", "Worktree name or branch name"), + .argument("", "Worktree name or branch name") + .option("--cwd ", "Repository directory (default: current)"), ).action(withOutput(runArchiveCommand)); return worktree; diff --git a/packages/cli/src/commands/worktree/ls.ts b/packages/cli/src/commands/worktree/ls.ts index cd388b2b08..43940131ce 100644 --- a/packages/cli/src/commands/worktree/ls.ts +++ b/packages/cli/src/commands/worktree/ls.ts @@ -1,6 +1,7 @@ import type { Command } from "commander"; import { basename } from "node:path"; import { connectToDaemon } from "../../utils/client.js"; +import { resolveRepositoryDirectory } from "./repository-directory.js"; import type { CommandOptions, ListResult, OutputSchema, CommandError } from "../../output/index.js"; /** Worktree list item for display */ @@ -40,12 +41,14 @@ export type WorktreeLsResult = ListResult; export interface WorktreeLsOptions extends CommandOptions { host?: string; + cwd?: string; } export async function runLsCommand( options: WorktreeLsOptions, _command: Command, ): Promise { + const cwd = resolveRepositoryDirectory(options); const client = await connectToDaemon({ target: options.daemonTarget }); try { @@ -53,7 +56,7 @@ export async function runLsCommand( const agents = agentsPayload.entries.map((entry) => entry.agent); // Get worktree list from daemon - const response = await client.getPaseoWorktreeList({}); + const response = await client.getPaseoWorktreeList({ cwd }); await client.close(); diff --git a/packages/cli/src/commands/worktree/repository-directory.ts b/packages/cli/src/commands/worktree/repository-directory.ts new file mode 100644 index 0000000000..3c241703c0 --- /dev/null +++ b/packages/cli/src/commands/worktree/repository-directory.ts @@ -0,0 +1,13 @@ +import { resolve } from "node:path"; +import type { DaemonTarget } from "../../utils/daemon-target.js"; + +export function resolveRepositoryDirectory(options: { + cwd?: string; + daemonTarget: DaemonTarget; +}): string { + const cwd = options.cwd ?? process.cwd(); + if (options.daemonTarget.kind === "instance") { + return resolve(cwd); + } + return cwd; +} diff --git a/packages/cli/tests/14-worktree.test.ts b/packages/cli/tests/14-worktree.test.ts index e0c4d7f9b5..027cfbaa2b 100644 --- a/packages/cli/tests/14-worktree.test.ts +++ b/packages/cli/tests/14-worktree.test.ts @@ -21,7 +21,10 @@ import assert from "node:assert"; import { getAvailablePort } from "./helpers/network.ts"; import { $ } from "zx"; -import { mkdtemp, rm } from "fs/promises"; +import { mkdtemp, rm, mkdir } from "fs/promises"; +import { execFile } from "node:child_process"; +import { promisify } from "node:util"; +import { runPaseoCli, startTestDaemon } from "./helpers/test-daemon.ts"; import { tmpdir } from "os"; import { join } from "path"; @@ -50,6 +53,7 @@ try { const result = await $`npx paseo worktree ls --help`.nothrow(); assert.strictEqual(result.exitCode, 0, "worktree ls --help should exit 0"); assert(result.stdout.includes("--host"), "help should mention --host option"); + assert(result.stdout.includes("--cwd"), "help should mention --cwd option"); console.log("✓ worktree ls --help shows options\n"); } @@ -86,6 +90,7 @@ try { const result = await $`npx paseo worktree archive --help`.nothrow(); assert.strictEqual(result.exitCode, 0, "worktree archive --help should exit 0"); assert(result.stdout.includes("--host"), "help should mention --host option"); + assert(result.stdout.includes("--cwd"), "help should mention --cwd option"); assert(result.stdout.includes(""), "help should mention required name argument"); console.log("✓ worktree archive --help shows options\n"); } @@ -167,4 +172,66 @@ try { await rm(paseoHome, { recursive: true, force: true }); } +// Exercise repository selection through the real CLI and daemon. An empty +// request used to fail before Git could inspect even a valid repository. +const daemon = await startTestDaemon(); +const git = promisify(execFile); +try { + const repo = join(daemon.workDir, "repository"); + await mkdir(repo); + await git("git", ["init", repo]); + await git("git", [ + "-C", + repo, + "-c", + "user.name=Test", + "-c", + "user.email=test@example.com", + "commit", + "--allow-empty", + "-m", + "Initial commit", + ]); + + for (const args of [ + ["worktree", "ls", "--json"], + ["worktree", "ls", "--cwd", repo, "--json"], + ["worktree", "ls", "--cwd", "./repository", "--json"], + ["worktree", "ls", "--host", `127.0.0.1:${daemon.port}`, "--cwd", ".", "--json"], + ]) { + const explicit = args.includes("--cwd"); + const result = await runPaseoCli(daemon, args, { + cwd: explicit ? daemon.workDir : repo, + env: { FORCE_COLOR: undefined }, + }); + assert.strictEqual(result.exitCode, 0, result.stderr); + assert.deepStrictEqual(JSON.parse(result.stdout), []); + } + + for (const args of [ + ["worktree", "archive", "missing", "--json"], + ["worktree", "archive", "missing", "--cwd", repo, "--json"], + ["worktree", "archive", "missing", "--cwd", "./repository", "--json"], + [ + "worktree", + "archive", + "missing", + "--host", + `127.0.0.1:${daemon.port}`, + "--cwd", + ".", + "--json", + ], + ]) { + const result = await runPaseoCli(daemon, args, { + cwd: args.includes("--cwd") ? daemon.workDir : repo, + env: { FORCE_COLOR: undefined }, + }); + assert.notStrictEqual(result.exitCode, 0); + assert.strictEqual(JSON.parse(result.stderr).error.code, "WORKTREE_NOT_FOUND", result.stderr); + } +} finally { + await daemon.stop(); +} + console.log("=== All worktree tests passed ===");