Skip to content

Add decodo screenshot command#6

Merged
paulius-krutkis-dcd merged 2 commits into
search-shortcutfrom
screenshot-shortcut
Jun 4, 2026
Merged

Add decodo screenshot command#6
paulius-krutkis-dcd merged 2 commits into
search-shortcutfrom
screenshot-shortcut

Conversation

@paulius-krutkis-dcd

@paulius-krutkis-dcd paulius-krutkis-dcd commented Jun 3, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds decodo screenshot for PNG captures via the universal target with headless: png. Builds on the tier-1 scrape/search stack in the base branch.

  • decodo screenshot <url>-o / --output writes a PNG file; pipe to stdout when not a TTY (refuses binary on TTY without -o)
  • -o file or directory — if the path is an existing directory, writes <host>.png derived from the URL (fallback screenshot.png); explicit file paths unchanged
  • Save feedback — prints Wrote <absolute path> on stderr after a successful file write
  • extractPngFromResponse — base64 from results[0].content, PNG signature validation
  • platform/services/writeBinaryOutput and resolveOutputFilePath for shared binary stdout and -o path resolution; defaultScreenshotFilename stays in scrape/services/
  • Scrape runnerrun-target-scrape in services/ with optional onResponse handler (passes scrape input for screenshot filename); scrape/search behavior unchanged
  • Help orderscreenshot listed after search in root --help

Test plan

  • pnpm test
  • decodo screenshot https://ip.decodo.com -o shot.png (stderr shows Wrote …)
  • decodo screenshot https://ip.decodo.com -o ./shots./shots/ip.decodo.com.png
  • decodo screenshot https://ip.decodo.com > shot.png (non-TTY)
  • decodo screenshot <url> on TTY without -o shows usage error

- Introduced a new `screenshot` command to capture PNG screenshots of specified URLs.
- Implemented options for geo targeting and output file specification.
- Added utility functions for handling binary output and extracting PNG data from responses.
- Updated command registration to include the new `createScreenshotCommand`.
- Enhanced tests to cover the new screenshot command functionality and validation scenarios.
@paulius-krutkis-dcd paulius-krutkis-dcd changed the title feat(screenshot): add screenshot command for capturing PNG images Add decodo screenshot command Jun 3, 2026
@@ -0,0 +1,5 @@
export interface ScreenshotOptions {
country?: string;
output?: string;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure all three are optional?

@paulius-krutkis-dcd paulius-krutkis-dcd Jun 4, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this mirrors command options:

    .option("--country <code>", "Geo / country code (maps to geo)")
    .option("-o, --output <path>", "Write PNG to file instead of stdout")
    .option("--target <name>", "Scrape target override (default: universal)")

output is only needed when stdout is a TTY
country and target have defaults in the command action (resolveTarget defaults to universal; geo is omitted if unset)

}

export function extractPngFromResponse(response: SyncResponse): Buffer {
const entry = response.results[0];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if result will be empty.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is handled in this check:

 if (entry === undefined) {
    throw new ValidationError("Screenshot response has no results.");
  }

@@ -0,0 +1,30 @@
import type { Command } from "commander";

export const ROOT_COMMAND_ORDER = [

@aurimas-angladagis-dcd aurimas-angladagis-dcd Jun 4, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if possible, but maybe we can create groups, like:

User:

  • setup;
  • reset;
  • etc;

Scrape:

  • scrape;
  • search;
  • screenshot
  • etc;

and when you will use --help it shows as grouped commands.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code is more about sorting commands which favor top level over target specific ones. Grouping is possible and can be done but I think it's product desicion, separate task

},
(response, options) => {
const opts = options as ScreenshotOptions;
writeBinaryOutput(extractPngFromResponse(response), {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about filename? Maybe we should have option for this too.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-o, --output already accepts a full file path (see line 16). If filename only variant is needed it can be added but I think the full path covers that use case

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about if path is a directory?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point, with previous changes it just throws which is bad ux, if folder is specified then by default it will save to a predefined filepath, updated pr description on how it works with new changes

Comment on lines +17 to +28
const orderIndex = new Map(order.map((name, index) => [name, index]));
const fallbackIndex = order.length;

return [...commands].sort((a, b) => {
const aIndex = orderIndex.get(a.name()) ?? fallbackIndex;
const bIndex = orderIndex.get(b.name()) ?? fallbackIndex;

if (aIndex !== bIndex) {
return aIndex - bIndex;
}

return a.name().localeCompare(b.name());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm having a hard time understanding reasoning here, but as I've understand the idea is to have ROOT_COMMAND_ORDER as first commands, then everything else?

@paulius-krutkis-dcd paulius-krutkis-dcd Jun 4, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you go it right. Not sure how to better write this, since there is no option for sort order in commander. Maybe better would be to have an export SORT_ORDER = ... for each command but that still requires a registry to map all the commands so it's kind of the same as it is right now

…pport

- Added functionality to specify a directory for output files in the screenshot command, defaulting to a generated filename based on the URL hostname.
- Refactored binary output handling to support writing to files or directories.
- Introduced utility functions for resolving output file paths and generating default screenshot filenames.
- Updated tests to validate new directory output behavior and filename generation.
- Removed obsolete `write-binary.ts` file and migrated its functionality to a new service.
@paulius-krutkis-dcd paulius-krutkis-dcd merged commit 0996da5 into search-shortcut Jun 4, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants