mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 21:03:19 +00:00
coding-agent: fix macOS screenshot filenames with unicode spaces (#181)
This commit is contained in:
parent
5c0a84b2d8
commit
9a7bbb2839
9 changed files with 70 additions and 108 deletions
|
|
@ -5,8 +5,8 @@
|
|||
import type { Attachment } from "@mariozechner/pi-agent-core";
|
||||
import chalk from "chalk";
|
||||
import { existsSync, readFileSync, statSync } from "fs";
|
||||
import { homedir } from "os";
|
||||
import { extname, resolve } from "path";
|
||||
import { resolveReadPath } from "../core/tools/path-utils.js";
|
||||
|
||||
/** Map of file extensions to MIME types for common image formats */
|
||||
const IMAGE_MIME_TYPES: Record<string, string> = {
|
||||
|
|
@ -23,17 +23,6 @@ function isImageFile(filePath: string): string | null {
|
|||
return IMAGE_MIME_TYPES[ext] || null;
|
||||
}
|
||||
|
||||
/** Expand ~ to home directory */
|
||||
function expandPath(filePath: string): string {
|
||||
if (filePath === "~") {
|
||||
return homedir();
|
||||
}
|
||||
if (filePath.startsWith("~/")) {
|
||||
return homedir() + filePath.slice(1);
|
||||
}
|
||||
return filePath;
|
||||
}
|
||||
|
||||
export interface ProcessedFiles {
|
||||
textContent: string;
|
||||
imageAttachments: Attachment[];
|
||||
|
|
@ -45,9 +34,8 @@ export function processFileArguments(fileArgs: string[]): ProcessedFiles {
|
|||
const imageAttachments: Attachment[] = [];
|
||||
|
||||
for (const fileArg of fileArgs) {
|
||||
// Expand and resolve path
|
||||
const expandedPath = expandPath(fileArg);
|
||||
const absolutePath = resolve(expandedPath);
|
||||
// Expand and resolve path (handles ~ expansion and macOS screenshot Unicode spaces)
|
||||
const absolutePath = resolve(resolveReadPath(fileArg));
|
||||
|
||||
// Check if file exists
|
||||
if (!existsSync(absolutePath)) {
|
||||
|
|
|
|||
|
|
@ -44,17 +44,21 @@ export interface LoadHooksResult {
|
|||
errors: Array<{ path: string; error: string }>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand path with ~ support.
|
||||
*/
|
||||
const UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g;
|
||||
|
||||
function normalizeUnicodeSpaces(str: string): string {
|
||||
return str.replace(UNICODE_SPACES, " ");
|
||||
}
|
||||
|
||||
function expandPath(p: string): string {
|
||||
if (p.startsWith("~/")) {
|
||||
return path.join(os.homedir(), p.slice(2));
|
||||
const normalized = normalizeUnicodeSpaces(p);
|
||||
if (normalized.startsWith("~/")) {
|
||||
return path.join(os.homedir(), normalized.slice(2));
|
||||
}
|
||||
if (p.startsWith("~")) {
|
||||
return path.join(os.homedir(), p.slice(1));
|
||||
if (normalized.startsWith("~")) {
|
||||
return path.join(os.homedir(), normalized.slice(1));
|
||||
}
|
||||
return p;
|
||||
return normalized;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,23 +1,10 @@
|
|||
import * as os from "node:os";
|
||||
import type { AgentTool } from "@mariozechner/pi-ai";
|
||||
import { Type } from "@sinclair/typebox";
|
||||
import * as Diff from "diff";
|
||||
import { constants } from "fs";
|
||||
import { access, readFile, writeFile } from "fs/promises";
|
||||
import { resolve as resolvePath } from "path";
|
||||
|
||||
/**
|
||||
* Expand ~ to home directory
|
||||
*/
|
||||
function expandPath(filePath: string): string {
|
||||
if (filePath === "~") {
|
||||
return os.homedir();
|
||||
}
|
||||
if (filePath.startsWith("~/")) {
|
||||
return os.homedir() + filePath.slice(1);
|
||||
}
|
||||
return filePath;
|
||||
}
|
||||
import { expandPath } from "./path-utils.js";
|
||||
|
||||
/**
|
||||
* Generate a unified diff string with line numbers and context
|
||||
|
|
|
|||
|
|
@ -3,24 +3,11 @@ import { Type } from "@sinclair/typebox";
|
|||
import { spawnSync } from "child_process";
|
||||
import { existsSync } from "fs";
|
||||
import { globSync } from "glob";
|
||||
import { homedir } from "os";
|
||||
import path from "path";
|
||||
import { ensureTool } from "../../utils/tools-manager.js";
|
||||
import { expandPath } from "./path-utils.js";
|
||||
import { DEFAULT_MAX_BYTES, formatSize, type TruncationResult, truncateHead } from "./truncate.js";
|
||||
|
||||
/**
|
||||
* Expand ~ to home directory
|
||||
*/
|
||||
function expandPath(filePath: string): string {
|
||||
if (filePath === "~") {
|
||||
return homedir();
|
||||
}
|
||||
if (filePath.startsWith("~/")) {
|
||||
return homedir() + filePath.slice(1);
|
||||
}
|
||||
return filePath;
|
||||
}
|
||||
|
||||
const findSchema = Type.Object({
|
||||
pattern: Type.String({
|
||||
description: "Glob pattern to match files, e.g. '*.ts', '**/*.json', or 'src/**/*.spec.ts'",
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ import type { AgentTool } from "@mariozechner/pi-ai";
|
|||
import { Type } from "@sinclair/typebox";
|
||||
import { spawn } from "child_process";
|
||||
import { readFileSync, type Stats, statSync } from "fs";
|
||||
import { homedir } from "os";
|
||||
import path from "path";
|
||||
import { ensureTool } from "../../utils/tools-manager.js";
|
||||
import { expandPath } from "./path-utils.js";
|
||||
import {
|
||||
DEFAULT_MAX_BYTES,
|
||||
formatSize,
|
||||
|
|
@ -15,19 +15,6 @@ import {
|
|||
truncateLine,
|
||||
} from "./truncate.js";
|
||||
|
||||
/**
|
||||
* Expand ~ to home directory
|
||||
*/
|
||||
function expandPath(filePath: string): string {
|
||||
if (filePath === "~") {
|
||||
return homedir();
|
||||
}
|
||||
if (filePath.startsWith("~/")) {
|
||||
return homedir() + filePath.slice(1);
|
||||
}
|
||||
return filePath;
|
||||
}
|
||||
|
||||
const grepSchema = Type.Object({
|
||||
pattern: Type.String({ description: "Search pattern (regex or literal string)" }),
|
||||
path: Type.Optional(Type.String({ description: "Directory or file to search (default: current directory)" })),
|
||||
|
|
|
|||
|
|
@ -1,23 +1,10 @@
|
|||
import type { AgentTool } from "@mariozechner/pi-ai";
|
||||
import { Type } from "@sinclair/typebox";
|
||||
import { existsSync, readdirSync, statSync } from "fs";
|
||||
import { homedir } from "os";
|
||||
import nodePath from "path";
|
||||
import { expandPath } from "./path-utils.js";
|
||||
import { DEFAULT_MAX_BYTES, formatSize, type TruncationResult, truncateHead } from "./truncate.js";
|
||||
|
||||
/**
|
||||
* Expand ~ to home directory
|
||||
*/
|
||||
function expandPath(filePath: string): string {
|
||||
if (filePath === "~") {
|
||||
return homedir();
|
||||
}
|
||||
if (filePath.startsWith("~/")) {
|
||||
return homedir() + filePath.slice(1);
|
||||
}
|
||||
return filePath;
|
||||
}
|
||||
|
||||
const lsSchema = Type.Object({
|
||||
path: Type.Optional(Type.String({ description: "Directory to list (default: current directory)" })),
|
||||
limit: Type.Optional(Type.Number({ description: "Maximum number of entries to return (default: 500)" })),
|
||||
|
|
|
|||
48
packages/coding-agent/src/core/tools/path-utils.ts
Normal file
48
packages/coding-agent/src/core/tools/path-utils.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import { accessSync, constants } from "node:fs";
|
||||
import * as os from "node:os";
|
||||
|
||||
const UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g;
|
||||
const NARROW_NO_BREAK_SPACE = "\u202F";
|
||||
|
||||
function normalizeUnicodeSpaces(str: string): string {
|
||||
return str.replace(UNICODE_SPACES, " ");
|
||||
}
|
||||
|
||||
function tryMacOSScreenshotPath(filePath: string): string {
|
||||
return filePath.replace(/ (AM|PM)\./g, `${NARROW_NO_BREAK_SPACE}$1.`);
|
||||
}
|
||||
|
||||
function fileExists(filePath: string): boolean {
|
||||
try {
|
||||
accessSync(filePath, constants.F_OK);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function expandPath(filePath: string): string {
|
||||
const normalized = normalizeUnicodeSpaces(filePath);
|
||||
if (normalized === "~") {
|
||||
return os.homedir();
|
||||
}
|
||||
if (normalized.startsWith("~/")) {
|
||||
return os.homedir() + normalized.slice(1);
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
export function resolveReadPath(filePath: string): string {
|
||||
const expanded = expandPath(filePath);
|
||||
|
||||
if (fileExists(expanded)) {
|
||||
return expanded;
|
||||
}
|
||||
|
||||
const macOSVariant = tryMacOSScreenshotPath(expanded);
|
||||
if (macOSVariant !== expanded && fileExists(macOSVariant)) {
|
||||
return macOSVariant;
|
||||
}
|
||||
|
||||
return expanded;
|
||||
}
|
||||
|
|
@ -1,24 +1,11 @@
|
|||
import * as os from "node:os";
|
||||
import type { AgentTool, ImageContent, TextContent } from "@mariozechner/pi-ai";
|
||||
import { Type } from "@sinclair/typebox";
|
||||
import { constants } from "fs";
|
||||
import { access, readFile } from "fs/promises";
|
||||
import { extname, resolve as resolvePath } from "path";
|
||||
import { resolveReadPath } from "./path-utils.js";
|
||||
import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, type TruncationResult, truncateHead } from "./truncate.js";
|
||||
|
||||
/**
|
||||
* Expand ~ to home directory
|
||||
*/
|
||||
function expandPath(filePath: string): string {
|
||||
if (filePath === "~") {
|
||||
return os.homedir();
|
||||
}
|
||||
if (filePath.startsWith("~/")) {
|
||||
return os.homedir() + filePath.slice(1);
|
||||
}
|
||||
return filePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map of file extensions to MIME types for common image formats
|
||||
*/
|
||||
|
|
@ -58,7 +45,7 @@ export const readTool: AgentTool<typeof readSchema> = {
|
|||
{ path, offset, limit }: { path: string; offset?: number; limit?: number },
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
const absolutePath = resolvePath(expandPath(path));
|
||||
const absolutePath = resolvePath(resolveReadPath(path));
|
||||
const mimeType = isImageFile(absolutePath);
|
||||
|
||||
return new Promise<{ content: (TextContent | ImageContent)[]; details: ReadToolDetails | undefined }>(
|
||||
|
|
|
|||
|
|
@ -1,21 +1,8 @@
|
|||
import * as os from "node:os";
|
||||
import type { AgentTool } from "@mariozechner/pi-ai";
|
||||
import { Type } from "@sinclair/typebox";
|
||||
import { mkdir, writeFile } from "fs/promises";
|
||||
import { dirname, resolve as resolvePath } from "path";
|
||||
|
||||
/**
|
||||
* Expand ~ to home directory
|
||||
*/
|
||||
function expandPath(filePath: string): string {
|
||||
if (filePath === "~") {
|
||||
return os.homedir();
|
||||
}
|
||||
if (filePath.startsWith("~/")) {
|
||||
return os.homedir() + filePath.slice(1);
|
||||
}
|
||||
return filePath;
|
||||
}
|
||||
import { expandPath } from "./path-utils.js";
|
||||
|
||||
const writeSchema = Type.Object({
|
||||
path: Type.String({ description: "Path to the file to write (relative or absolute)" }),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue