feat: add Tauri desktop app support and terminal management
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
import {
|
||||
stripShellWrapperPrefix,
|
||||
extractPrincipalParam,
|
||||
stripCwdPrefix,
|
||||
} from "./tool-call-parsers.js";
|
||||
|
||||
describe("stripShellWrapperPrefix", () => {
|
||||
test("strips /bin/zsh -lc cd path && prefix", () => {
|
||||
const command = "/bin/zsh -lc cd /Users/moboudra/dev/blankpage/editor && npm run format";
|
||||
expect(stripShellWrapperPrefix(command)).toBe("npm run format");
|
||||
});
|
||||
|
||||
test("strips /bin/zsh -c cd path && prefix", () => {
|
||||
const command = "/bin/zsh -c cd /path/to/project && git status";
|
||||
expect(stripShellWrapperPrefix(command)).toBe("git status");
|
||||
});
|
||||
|
||||
test("strips /bin/bash -lc cd path && prefix", () => {
|
||||
const command = "/bin/bash -lc cd /home/user/project && npm test";
|
||||
expect(stripShellWrapperPrefix(command)).toBe("npm test");
|
||||
});
|
||||
|
||||
test("strips /bin/sh -c cd path && prefix", () => {
|
||||
const command = "/bin/sh -c cd /tmp && ls -la";
|
||||
expect(stripShellWrapperPrefix(command)).toBe("ls -la");
|
||||
});
|
||||
|
||||
test("returns command unchanged when no prefix", () => {
|
||||
const command = "npm run build";
|
||||
expect(stripShellWrapperPrefix(command)).toBe("npm run build");
|
||||
});
|
||||
|
||||
test("returns command unchanged for partial match", () => {
|
||||
const command = "/bin/zsh -lc npm run build";
|
||||
expect(stripShellWrapperPrefix(command)).toBe("/bin/zsh -lc npm run build");
|
||||
});
|
||||
|
||||
test("handles paths with spaces in quotes", () => {
|
||||
// The regex matches \S+ for the path, so quoted paths won't fully match
|
||||
// This is acceptable - quoted paths are edge cases
|
||||
const command = '/bin/zsh -lc cd "/path with spaces" && npm test';
|
||||
// Won't strip because the path pattern expects non-whitespace
|
||||
expect(stripShellWrapperPrefix(command)).toBe('/bin/zsh -lc cd "/path with spaces" && npm test');
|
||||
});
|
||||
});
|
||||
|
||||
describe("extractPrincipalParam", () => {
|
||||
test("extracts and strips shell wrapper from command string", () => {
|
||||
const args = { command: "/bin/zsh -lc cd /Users/dev/project && npm run format" };
|
||||
expect(extractPrincipalParam(args)).toBe("npm run format");
|
||||
});
|
||||
|
||||
test("extracts and strips shell wrapper from command array", () => {
|
||||
const args = { command: ["/bin/bash", "-lc", "cd /path && git status"] };
|
||||
// Array is joined with spaces first: "/bin/bash -lc cd /path && git status"
|
||||
// Then shell wrapper is stripped
|
||||
expect(extractPrincipalParam(args)).toBe("git status");
|
||||
});
|
||||
|
||||
test("extracts plain command without shell wrapper", () => {
|
||||
const args = { command: "npm run build" };
|
||||
expect(extractPrincipalParam(args)).toBe("npm run build");
|
||||
});
|
||||
|
||||
test("extracts file_path and strips cwd", () => {
|
||||
const args = { file_path: "/Users/dev/project/src/file.ts" };
|
||||
expect(extractPrincipalParam(args, "/Users/dev/project")).toBe("src/file.ts");
|
||||
});
|
||||
|
||||
test("extracts pattern without modification", () => {
|
||||
const args = { pattern: "*.ts" };
|
||||
expect(extractPrincipalParam(args)).toBe("*.ts");
|
||||
});
|
||||
|
||||
test("extracts query without modification", () => {
|
||||
const args = { query: "search term" };
|
||||
expect(extractPrincipalParam(args)).toBe("search term");
|
||||
});
|
||||
|
||||
test("extracts url without modification", () => {
|
||||
const args = { url: "https://example.com" };
|
||||
expect(extractPrincipalParam(args)).toBe("https://example.com");
|
||||
});
|
||||
|
||||
test("returns undefined for empty object", () => {
|
||||
expect(extractPrincipalParam({})).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("stripCwdPrefix", () => {
|
||||
test("strips cwd prefix from file path", () => {
|
||||
expect(stripCwdPrefix("/Users/dev/project/src/file.ts", "/Users/dev/project")).toBe("src/file.ts");
|
||||
});
|
||||
|
||||
test("returns . for exact cwd match", () => {
|
||||
expect(stripCwdPrefix("/Users/dev/project", "/Users/dev/project")).toBe(".");
|
||||
});
|
||||
|
||||
test("returns path unchanged when no cwd prefix", () => {
|
||||
expect(stripCwdPrefix("/other/path/file.ts", "/Users/dev/project")).toBe("/other/path/file.ts");
|
||||
});
|
||||
|
||||
test("returns path unchanged when no cwd provided", () => {
|
||||
expect(stripCwdPrefix("/Users/dev/project/file.ts")).toBe("/Users/dev/project/file.ts");
|
||||
});
|
||||
});
|
||||
@@ -18,9 +18,9 @@ const PrincipalParamSchema = z.union([
|
||||
z.object({ filePath: z.string() }).transform((d) => ({ type: "path" as const, value: d.filePath })),
|
||||
z.object({ path: z.string() }).transform((d) => ({ type: "path" as const, value: d.path })),
|
||||
// Command as string
|
||||
z.object({ command: z.string() }).transform((d) => ({ type: "text" as const, value: d.command })),
|
||||
z.object({ command: z.string() }).transform((d) => ({ type: "command" as const, value: d.command })),
|
||||
// Command as array (Codex sends this)
|
||||
z.object({ command: z.array(z.string()).nonempty() }).transform((d) => ({ type: "text" as const, value: d.command.join(" ") })),
|
||||
z.object({ command: z.array(z.string()).nonempty() }).transform((d) => ({ type: "command" as const, value: d.command.join(" ") })),
|
||||
// Other text params
|
||||
z.object({ pattern: z.string() }).transform((d) => ({ type: "text" as const, value: d.pattern })),
|
||||
z.object({ query: z.string() }).transform((d) => ({ type: "text" as const, value: d.query })),
|
||||
@@ -53,6 +53,14 @@ export function stripCwdPrefix(filePath: string, cwd?: string): string {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
// Strips shell wrapper prefixes like "/bin/zsh -lc cd /path && " from commands
|
||||
// This is used for display purposes to show the actual command being run
|
||||
const SHELL_WRAPPER_PATTERN = /^\/bin\/(?:zsh|bash|sh)\s+(?:-[a-zA-Z]+\s+)?cd\s+\S+\s+&&\s+/;
|
||||
|
||||
export function stripShellWrapperPrefix(command: string): string {
|
||||
return command.replace(SHELL_WRAPPER_PATTERN, "");
|
||||
}
|
||||
|
||||
export function extractPrincipalParam(args: unknown, cwd?: string): string | undefined {
|
||||
const parsed = PrincipalParamSchema.safeParse(args);
|
||||
if (!parsed.success) {
|
||||
@@ -60,7 +68,13 @@ export function extractPrincipalParam(args: unknown, cwd?: string): string | und
|
||||
}
|
||||
|
||||
const { type, value } = parsed.data;
|
||||
return type === "path" ? stripCwdPrefix(value, cwd) : value;
|
||||
if (type === "path") {
|
||||
return stripCwdPrefix(value, cwd);
|
||||
}
|
||||
if (type === "command") {
|
||||
return stripShellWrapperPrefix(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
// ---- TodoWrite Extraction ----
|
||||
|
||||
Reference in New Issue
Block a user