-
Notifications
You must be signed in to change notification settings - Fork 57
Open
Description
I always produce this error on Windows.
[CLI subprocess] '\"copilot\"' is not recognized as an internal or external command,
[CLI subprocess] operable program or batch file.
Here is my code:
import { CopilotClient } from "@github/copilot-sdk";
import { promises as fs } from "node:fs";
import path from "node:path";
const IMAGE_EXTENSIONS = new Set([
".jpg",
".jpeg",
".png",
".gif",
".webp",
".bmp",
".tiff",
".tif",
".heic",
".heif",
]);
const SKIP_DIRS = new Set(["node_modules", ".git", "dist"]);
type WalkOptions = {
root: string;
recursive: boolean;
};
async function walkDir({ root, recursive }: WalkOptions): Promise<string[]> {
const entries = await fs.readdir(root, { withFileTypes: true });
const files: string[] = [];
for (const entry of entries) {
const entryPath = path.join(root, entry.name);
if (entry.isDirectory()) {
if (SKIP_DIRS.has(entry.name)) {
continue;
}
if (recursive) {
files.push(...(await walkDir({ root: entryPath, recursive })));
}
continue;
}
if (entry.isFile()) {
files.push(entryPath);
}
}
return files;
}
function isImageFile(filePath: string): boolean {
return IMAGE_EXTENSIONS.has(path.extname(filePath).toLowerCase());
}
function buildPrompt(fileName: string): string {
return [
"Describe this photo in concise, factual terms.",
"Focus on visible objects, scene, lighting, and any notable details.",
"Return plain text only, no markdown, no bullet points.",
`File name: ${fileName}`,
].join(" ");
}
async function writeDescription(filePath: string, description: string) {
const dirName = path.dirname(filePath);
const baseName = path.basename(filePath, path.extname(filePath));
const txtPath = path.join(dirName, `${baseName}.txt`);
await fs.writeFile(txtPath, description.trim() + "\n", "utf8");
}
function printHelp() {
console.log(`Usage: photo-agent [options]
Options:
-r, --recursive Recurse into subdirectories
-C, --working-dir Working directory (default: current)
--model=<name> Model name (default: gpt-5)
-h, --help Show help
`);
}
function readWorkingDir(args: string[]): string | undefined {
const longPrefix = "--working-dir=";
const shortPrefix = "-C=";
for (let i = 0; i < args.length; i += 1) {
const arg = args[i];
if (arg.startsWith(longPrefix)) {
return arg.slice(longPrefix.length);
}
if (arg.startsWith(shortPrefix)) {
return arg.slice(shortPrefix.length);
}
if (arg === "--working-dir" || arg === "-C") {
const next = args[i + 1];
if (!next || next.startsWith("-")) {
throw new Error("Missing value for --working-dir/-C.");
}
return next;
}
}
return undefined;
}
async function main() {
const argv = process.argv.slice(2);
const args = new Set(argv);
if (args.has("--help") || args.has("-h")) {
printHelp();
return;
}
const recursive = args.has("--recursive") || args.has("-r");
const modelArg = argv.find((arg) => arg.startsWith("--model="));
const model = modelArg ? modelArg.split("=")[1] : "gpt-5";
let cwd = process.cwd();
try {
const workingDir = readWorkingDir(argv);
if (workingDir) {
cwd = path.resolve(workingDir);
}
} catch (error) {
console.error(error instanceof Error ? error.message : error);
process.exitCode = 1;
return;
}
const allFiles = await walkDir({ root: cwd, recursive });
const images = allFiles.filter(isImageFile);
if (images.length === 0) {
console.log("No image files found.");
return;
}
const client = new CopilotClient();
await client.start();
const session = await client.createSession({ model });
try {
for (const imagePath of images) {
const fileName = path.basename(imagePath);
console.log(`Describing ${fileName}...`);
const event = await session.sendAndWait({
prompt: buildPrompt(fileName),
attachments: [
{
type: "file",
path: imagePath,
displayName: fileName,
},
],
});
const content = event?.data?.content?.trim();
if (!content) {
console.warn(`No description returned for ${fileName}. Skipping.`);
continue;
}
await writeDescription(imagePath, content);
}
} finally {
await session.destroy();
await client.stop();
}
}
main().catch((error) => {
console.error("Failed to run photo agent:", error);
process.exitCode = 1;
});Metadata
Metadata
Assignees
Labels
No labels