From 5e07c05e54c4fc0e57b5f26cb2bf994a4071b1f9 Mon Sep 17 00:00:00 2001 From: patrikbraborec Date: Tue, 24 Mar 2026 11:26:40 +0100 Subject: [PATCH 1/2] feat: add `actors search` command to search Apify Store Adds a new `actors search` subcommand that searches the Apify Store for Actors by query, category, author, pricing model, and more. Uses the apify-client SDK's StoreCollectionClient for consistency with all other commands. Does not require authentication. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/reference.md | 62 +++++++++++---- scripts/generate-cli-docs.ts | 1 + src/commands/actors/_index.ts | 2 + src/commands/actors/search.ts | 139 ++++++++++++++++++++++++++++++++++ 4 files changed, 191 insertions(+), 13 deletions(-) create mode 100644 src/commands/actors/search.ts diff --git a/docs/reference.md b/docs/reference.md index 5fb1714b0..ea2658199 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -574,19 +574,20 @@ DESCRIPTION Manages Actor creation, deployment, and execution on the Apify platform. SUBCOMMANDS - actors start Starts Actor remotely and returns run details - immediately. - actors rm Permanently removes an Actor from your account. - actors push Deploys Actor to Apify platform using settings from - '.actor/actor.json'. - actors pull Download Actor code to current directory. Clones Git - repositories or fetches Actor files based on the source type. - actors ls Prints a list of recently executed Actors or Actors - you own. - actors info Get information about an Actor. - actors call Executes Actor remotely using your authenticated - account. - actors build Creates a new build of the Actor. + actors start Starts Actor remotely and returns run details + immediately. + actors rm Permanently removes an Actor from your account. + actors search Searches Actors in the Apify Store. + actors push Deploys Actor to Apify platform using settings + from '.actor/actor.json'. + actors pull Download Actor code to current directory. Clones + Git repositories or fetches Actor files based on the source type. + actors ls Prints a list of recently executed Actors or + Actors you own. + actors info Get information about an Actor. + actors call Executes Actor remotely using your authenticated + account. + actors build Creates a new build of the Actor. ``` ##### `apify actors ls` @@ -608,6 +609,41 @@ FLAGS --offset= Number of Actors that will be skipped. ``` +##### `apify actors search` + +```sh +DESCRIPTION + Searches Actors in the Apify Store. + + Searches the Apify Store for Actors matching the given query. Results can be + filtered by category, author, pricing model, and more. This command does not + require authentication. + +USAGE + $ apify actors search [query] [--category ] + [--json] [--limit ] [--offset ] + [--pricing-model ] [--sort-by ] + [--username ] + +ARGUMENTS + query Search query to find Actors by title, name, description, username, + or readme. + +FLAGS + --category= Filter by category (e.g. + AI). + --json Format the command output as + JSON + --limit= Maximum number of results to + return. + --offset= Number of results to skip + for pagination. + --pricing-model= Filter by pricing model. + --sort-by= Sort order for the results. + --username= Filter by Actor author + username. +``` + ##### `apify actors rm` ```sh diff --git a/scripts/generate-cli-docs.ts b/scripts/generate-cli-docs.ts index 85072ee93..1e9d6d93f 100644 --- a/scripts/generate-cli-docs.ts +++ b/scripts/generate-cli-docs.ts @@ -39,6 +39,7 @@ const categories: Record = { // { command: Commands.actors }, { command: Commands.actorsLs }, + { command: Commands.actorsSearch }, { command: Commands.actorsRm }, ], 'actor-deploy': [ diff --git a/src/commands/actors/_index.ts b/src/commands/actors/_index.ts index 43a6034fc..ebec3cf92 100644 --- a/src/commands/actors/_index.ts +++ b/src/commands/actors/_index.ts @@ -6,6 +6,7 @@ import { ActorsLsCommand } from './ls.js'; import { ActorsPullCommand } from './pull.js'; import { ActorsPushCommand } from './push.js'; import { ActorsRmCommand } from './rm.js'; +import { ActorsSearchCommand } from './search.js'; import { ActorsStartCommand } from './start.js'; export class ActorsIndexCommand extends ApifyCommand { @@ -17,6 +18,7 @@ export class ActorsIndexCommand extends ApifyCommand // ActorsStartCommand, ActorsRmCommand, + ActorsSearchCommand, ActorsPushCommand, ActorsPullCommand, ActorsLsCommand, diff --git a/src/commands/actors/search.ts b/src/commands/actors/search.ts new file mode 100644 index 000000000..4352c7b28 --- /dev/null +++ b/src/commands/actors/search.ts @@ -0,0 +1,139 @@ +import type { ActorStoreList } from 'apify-client'; +import { ApifyClient } from 'apify-client'; +import chalk from 'chalk'; + +import { ApifyCommand } from '../../lib/command-framework/apify-command.js'; +import { Args } from '../../lib/command-framework/args.js'; +import { Flags } from '../../lib/command-framework/flags.js'; +import { CompactMode, ResponsiveTable } from '../../lib/commands/responsive-table.js'; +import { CommandExitCodes } from '../../lib/consts.js'; +import { error, info, simpleLog } from '../../lib/outputs.js'; +import { getApifyClientOptions, printJsonToStdout } from '../../lib/utils.js'; + +const pricingModelLabels: Record = { + FREE: 'Free', + FLAT_PRICE_PER_MONTH: 'Subscription', + PRICE_PER_DATASET_ITEM: 'Pay per result', + PAY_PER_EVENT: 'Pay per event', +}; + +function formatPricingModel(model?: string): string { + if (!model) return chalk.gray('Unknown'); + + return pricingModelLabels[model] ?? model; +} + +function truncateDescription(description?: string, maxLength = 60): string { + if (!description) return ''; + + if (description.length <= maxLength) return description; + + return `${description.slice(0, maxLength - 1)}…`; +} + +export class ActorsSearchCommand extends ApifyCommand { + static override name = 'search' as const; + + static override description = 'Searches Actors in the Apify Store.\n\nSearches the Apify Store for Actors matching the given query. Results can be filtered by category, author, pricing model, and more. This command does not require authentication.'; + + static override args = { + query: Args.string({ + description: 'Search query to find Actors by title, name, description, username, or readme.', + required: false, + }), + }; + + static override flags = { + 'sort-by': Flags.string({ + description: 'Sort order for the results.', + options: ['relevance', 'popularity', 'newest', 'lastUpdate'], + default: 'relevance', + }), + category: Flags.string({ + description: 'Filter by category (e.g. AI).', + }), + username: Flags.string({ + description: 'Filter by Actor author username.', + }), + 'pricing-model': Flags.string({ + description: 'Filter by pricing model.', + options: ['FREE', 'FLAT_PRICE_PER_MONTH', 'PRICE_PER_DATASET_ITEM', 'PAY_PER_EVENT'], + }), + limit: Flags.integer({ + description: 'Maximum number of results to return.', + default: 20, + }), + offset: Flags.integer({ + description: 'Number of results to skip for pagination.', + default: 0, + }), + }; + + static override enableJsonFlag = true; + + async run() { + const { query } = this.args; + const { json, sortBy, category, username, pricingModel, limit, offset } = this.flags; + + const client = new ApifyClient(getApifyClientOptions()); + + let result; + + try { + result = await client.store().list({ + search: query, + sortBy, + category, + username, + pricingModel, + limit, + offset, + }); + } catch (err) { + process.exitCode = CommandExitCodes.RunFailed; + error({ + message: `Failed to search Apify Store: ${err instanceof Error ? err.message : String(err)}`, + stdout: true, + }); + return; + } + + if (result.count === 0) { + if (json) { + printJsonToStdout(result); + return; + } + + info({ message: 'No Actors found matching your search.', stdout: true }); + return; + } + + if (json) { + printJsonToStdout(result); + return; + } + + const table = new ResponsiveTable({ + allColumns: ['Name', 'Description', 'Users (30d)', 'Pricing'], + mandatoryColumns: ['Name', 'Pricing'], + columnAlignments: { + 'Users (30d)': 'right', + Name: 'left', + }, + }); + + for (const item of result.items as ActorStoreList[]) { + table.pushRow({ + Name: `${item.title}\n${chalk.gray(`${item.username}/${item.name}`)}`, + Description: truncateDescription(item.description), + 'Users (30d)': chalk.cyan(`${item.stats?.totalUsers30Days ?? 0}`), + Pricing: formatPricingModel(item.currentPricingInfo?.pricingModel), + }); + } + + simpleLog({ + message: table.render(CompactMode.WebLikeCompact), + stdout: true, + }); + } +} From f7334a28b3032b3f3fa50ea8a2e4f4566daa6db2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20Sol=C3=A1r?= Date: Tue, 24 Mar 2026 17:58:11 +0100 Subject: [PATCH 2/2] lint:fix --- src/commands/actors/search.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/commands/actors/search.ts b/src/commands/actors/search.ts index 4352c7b28..8dd77661e 100644 --- a/src/commands/actors/search.ts +++ b/src/commands/actors/search.ts @@ -34,7 +34,8 @@ function truncateDescription(description?: string, maxLength = 60): string { export class ActorsSearchCommand extends ApifyCommand { static override name = 'search' as const; - static override description = 'Searches Actors in the Apify Store.\n\nSearches the Apify Store for Actors matching the given query. Results can be filtered by category, author, pricing model, and more. This command does not require authentication.'; + static override description = + 'Searches Actors in the Apify Store.\n\nSearches the Apify Store for Actors matching the given query. Results can be filtered by category, author, pricing model, and more. This command does not require authentication.'; static override args = { query: Args.string({