Show folder content in custom context menu

And here is a slightly improved version of the script that also shows the app icon when the file is an app. This requires the latest BTT alpha 4.717 though - with previous versions the icons would be huge.

image

async function retrieveJSON() {
  let folder = "/Applications";
  let tags = ["Red", "Green"];
  
  // Join tags to form the search query, each tag must be checked separately with an OR condition
  let tagQuery = tags.map(tag => `kMDItemUserTags == '${tag}'`).join(" || ");
  
  // Construct the shell script with the folder and tags
  let script = `mdfind -onlyin ${folder} "${tagQuery}"`;

  let filePaths = await runShellScript({
    script: script,
  });

  // Split the file paths by new line
  let fileArray = filePaths.split("\n").filter(Boolean); // filter(Boolean) removes any empty lines

  // Create an array of objects with filePath and fileName
  let files = fileArray.map((filePath) => {
    let fileName = filePath.split("/").pop(); // Extract the file name from the path
    return { filePath, fileName };
  });

  let menuItems = [];

  // Function to retrieve the icon path by reading the Info.plist
  async function getAppIconPath(filePath) {
    // Construct the path to the Info.plist file
    let plistPath = `${filePath}/Contents/Info.plist`;

    // Run a shell script to retrieve the CFBundleIconFile from the Info.plist
    let iconName = await runShellScript({
      script: `defaults read "${plistPath}" CFBundleIconFile || echo "AppIcon"`,
    });

    // Add the .icns extension if not present
    iconName = iconName.trim();
    if (!iconName.endsWith(".icns")) {
      iconName += ".icns";
    }

    // Return the full path to the .icns file
    return `${filePath}/Contents/Resources/${iconName}`;
  }

  // Create the context menu items that trigger a shell script to open the file
  for (let file of files) {
    let iconPath = await getAppIconPath(file.filePath); // Get the correct icon path from Info.plist
    let item = {
      title: file.fileName,
      action: `js::(async () => {runShellScript({script: 'open "${file.filePath}"'})})()`,
      icon: `path::${iconPath}::width@@30`
    };
    menuItems.push(item);
  }

  return JSON.stringify(menuItems);
}

Instead of a context menu, you can also use this script with the "Choose From List" action: