Skip to content

feat(create): guided wizard with use-case and language filters - #1278

Open
l2ysho wants to merge 14 commits into
masterfrom
1236-guided-apify-create-wizard-use-case-language-best-match-template
Open

feat(create): guided wizard with use-case and language filters#1278
l2ysho wants to merge 14 commits into
masterfrom
1236-guided-apify-create-wizard-use-case-language-best-match-template

Conversation

@l2ysho

@l2ysho l2ysho commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Closes #1236.

Reshapes apify create into the guided wizard: use case → language → best-match template, plus a new apify templates ls command. Local scaffold only (--source apify).

What changed

  • Wizard flow in apify create: prompts "What do you want to build?" (use case) and language, then one scrollable, fit-ranked template list — exact matches first (top preselected), a separator, then closest alternatives.
  • New flags -u/--use-case, -l/--language. -t/--template stays authoritative and bypasses the wizard; -u/-l are ignored when -t is given.
  • New command apify templates ls [--json]--json emits full template objects including useCases[].
  • Ported getTemplateRecommendation from apify-core with two CLI changes: per-template isExactMatch and no result limit.

Flag → manifest mapping

--use-case manifest id --language (+ alias) category
web-scraper WEB_SCRAPING javascript / js javascript
ai-agent AI typescript / ts typescript
data-pipeline INTEGRATION python / py python
browser-automation AUTOMATION

"Any use case" and "Any language" both mean no filter.

Test plan

  • Unit tests: getTemplateRecommendation (tiers, dedup, any-language, skip-use-case, no-exact) and buildTemplateChoiceList (separator, labels, hint).
  • build / lint / format clean; docs/ regenerated.
  • Drove the wizard end-to-end in a terminal.

🤖 Generated with Claude Code

Reshape `apify create` into a guided wizard: prompt for what the user
wants to build (use case) and the language, then present a single
fit-ranked template list (exact matches first, top preselected, a
non-selectable separator, then the closest alternatives).

- Add `-u/--use-case` and `-l/--language` filter flags; `-t/--template`
  stays authoritative and bypasses the wizard.
- Add `apify templates ls [--json]`, exposing each template's
  useCases[] so agents can discover the enum values.
- Port apify-core's getTemplateRecommendation tier algorithm, with a
  per-template isExactMatch flag (no aggregate) and no result limit;
  a skipped use case and "any language" both mean "no filter".

Refs #1236

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions github-actions Bot added this to the 145th sprint - Tooling team milestone Jul 16, 2026
@github-actions github-actions Bot added t-tooling Issues with this label are in the ownership of the tooling team. tested Temporary label used only programatically for some analytics. labels Jul 16, 2026
- Assert `--use-case` / `--language` are ignored when `--template` is
  provided: a TypeScript template scaffolds unchanged despite conflicting
  python/ai filters.
- Unit-test the flag→id mappings (useCaseFlagToId, USE_CASE/LANGUAGE
  flag choices, labels), including that STARTER is never exposed.

Refs #1236

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@l2ysho l2ysho added t-builders Issues owned by the Builders team. and removed t-tooling Issues with this label are in the ownership of the tooling team. labels Jul 16, 2026
@l2ysho
l2ysho marked this pull request as ready for review July 16, 2026 10:56
Comment thread src/commands/create.ts Outdated
Comment thread docs/reference.md Outdated
USAGE
$ apify create [actorName] [--omit-optional-deps]
$ apify create [actorName]
[-l javascript|typescript|python|other] [--omit-optional-deps]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggestion: let's support aliases as well.
javascript / js
typescript / ts
python / py

Worth to consider whether to support case insesitivity, e.g. TS, Python and JavaScript as valid options.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Important: as no template currently matches the other, let's drop it.
The concept of Templates is to be changed anyway and once they become Actors, a language will be most likely inheritent (there will be no Actor without a clearly defined language).

Comment thread docs/reference.md Outdated
$ apify create [actorName]
[-l javascript|typescript|python|other] [--omit-optional-deps]
[--skip-dependency-install] [--skip-git-init] [-t <value>]
[-u web-scraping|ai-agent|api-pipeline|browser-automation]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggestion: now seeing it, web-scraper would fit better as it describes the identity of the Actor (same as ai-agent).

Also, api-pipeline sounds a bit weird. In Console, it is API and data pipeline, so maybe data-pipeline would fit better? 🤔

Comment on lines +10 to +17
export const TEMPLATE_USE_CASES = {
/** Entry-level scaffold for a user's first Actor. Not exposed as a wizard option; used by the fallback tiers. */
STARTER: 'STARTER',
WEB_SCRAPING: 'WEB_SCRAPING',
INTEGRATION: 'INTEGRATION',
AUTOMATION: 'AUTOMATION',
AI: 'AI',
} as const;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit:

might be worth having the description of each of the tags documented here as well, similar to how it is done in Console:

/**
 * Canonical use case tags for Actor templates. Each template in the manifest carries one or more
 * of these in its `useCases` array. Templates should be tagged for what they ARE (their primary
 * purpose / framing), not for what they COULD become — that keeps wizard filters meaningful.
 */
export const TEMPLATE_USE_CASES = {
    /** Entry-level scaffold for a user's first Actor — language quickstarts, empty projects, standby starters, generic wrappers (e.g. cli-start). */
    STARTER: 'STARTER',
    /** Extracting structured data from websites — HTTP scrapers, browser scrapers, crawlers. The template's primary output is scraped data. */
    WEB_SCRAPING: 'WEB_SCRAPING',
    /** Bridges Apify with external systems — Standby mode (serves HTTP), MCP servers (callable by AI clients), or templates that call out to external APIs, services, or CLIs. */
    INTEGRATION: 'INTEGRATION',
    /** Performs actions rather than extracting data — test runners, browser automation (Playwright/Selenium/Cypress used for interaction), CLI orchestration. */
    AUTOMATION: 'AUTOMATION',
    /** LLM-powered agents, AI tool servers (MCP), and AI framework integration demos (LangChain, CrewAI, etc.). */
    AI: 'AI',
} as const;

Comment thread src/lib/templates/consts.ts Outdated
Comment on lines +42 to +45
/** kebab-case value accepted by the `--use-case` flag. */
flag: string;
/** Manifest id this maps to (value in `useCases[]`). */
id: string;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: a bit clearer naming might be beneficial:

Suggested change
/** kebab-case value accepted by the `--use-case` flag. */
flag: string;
/** Manifest id this maps to (value in `useCases[]`). */
id: string;
/** kebab-case value accepted by the `--use-case` flag. */
cliFlag: string;
/** Template tag this maps to (value in `useCases[]`). */
templateTag: string;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: the types of these might be specific.

Option A: define the UseCaseOption after USE_CASE_OPTIONS and derive it from it, e.g.

export const USE_CASE_OPTIONS = [
	{ flag: 'web-scraping', id: TEMPLATE_USE_CASES.WEB_SCRAPING, label: 'Web scraping' },
	{ flag: 'ai-agent', id: TEMPLATE_USE_CASES.AI, label: 'AI agent' },
	{ flag: 'api-pipeline', id: TEMPLATE_USE_CASES.INTEGRATION, label: 'API & data pipeline' },
	{ flag: 'browser-automation', id: TEMPLATE_USE_CASES.AUTOMATION, label: 'Browser automation' },
] as const;

export interface UseCaseOption {
	/** kebab-case value accepted by the `--use-case` flag. */
	cliFlag: (typeof USE_CASE_OPTIONS)[number]['cliFlag'];
	/** Template tag this maps to (value in `useCases[]`). */
	templateTag: (typeof USE_CASE_OPTIONS)[number]['templateTag'];
    ...

Option B: defined the CliFlag and TemplateUseCaseTag type beforehand, e.g.:

type CliUseCaseFlag = 'web-scraping' | 'ai-agent' | ...
type TemplateUseCaseTag ...
type CliLanguageTag = ...
type TemplateLanguageTag = ...

Personally I would lean Option A

Comment thread src/lib/templates/consts.ts Outdated
Comment on lines +62 to +63
/** Manifest `category`, which is also the value accepted by the `--language` flag. */
id: string;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: same as above, templateTag might be a bit clearer.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same as above, the type here might be more specific by deriving from the USE_CASE_OPTIONS

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: also consider to swallow the dusplicity and have both cliFlag and templateTag here even tho they are the same.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

FYI, a consolidation epic to reduce copied-over code in CLI:
#1308

Hopefully we will get to it one day :D

Comment on lines +31 to +32
const hasLanguageFilter = languageId !== ANY_TEMPLATE_LANGUAGE;
const hasUseCaseFilter = useCaseId !== undefined;

@DaveHanns DaveHanns Jul 28, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggestion: with the latest decision, effectively "any use case" is comming to the Console as well. The plan there is to mimic the language mechanics and have ANY_TEMPLATE_USE_CASE.

I think we should do the same in CLI as well, so instead of accepting undefined useCaseId, broadening the options with ANY_TEMPLATE_USE_CASE.

That way the mechanics of language and use-case will be the same.

const hasUseCaseFilter = useCaseId !== undefined;

const matchesLanguage = (template: Template) => !hasLanguageFilter || template.category === languageId;
const matchesUseCase = (template: Template) => !hasUseCaseFilter || (template.useCases?.includes(useCaseId) ?? false);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Super nit:

Suggested change
const matchesUseCase = (template: Template) => !hasUseCaseFilter || (template.useCases?.includes(useCaseId) ?? false);
const matchesUseCase = (template: Template) => !hasUseCaseFilter || !!template.useCases?.includes(useCaseId);

Comment thread src/lib/templates/getTemplateRecommendation.ts Outdated
l2ysho and others added 7 commits July 28, 2026 21:57
Co-authored-by: Edyta <142720610+szaganek@users.noreply.github.com>
Address PR review: `web-scraper` reads as an Actor identity (like
`ai-agent`), and `data-pipeline` matches Console's "API & data pipeline"
better than `api-pipeline`. Prompt labels are unchanged.

Refs #1236

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Document each TEMPLATE_USE_CASES tag (per-key JSDoc).
- Rename option fields to cliFlag / templateTag and derive UseCaseOption
  and LanguageOption from the `as const` option arrays.
- Correct the --use-case flag description ("by use case", not category).
- Simplify matchesUseCase to `!!...includes(...)`.

Refs #1236

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Accept `js`/`ts`/`py` as aliases for `--language`, resolved to the
  manifest category via a new `languageFlagToTag`.
- Drop the `other` (BYO-Docker) language value: no template matches it,
  and language becomes inherent once templates turn into Actors.

Refs #1236

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Replace the `undefined` "skip use case" sentinel with an
  ANY_TEMPLATE_USE_CASE marker, mirroring ANY_TEMPLATE_LANGUAGE.
- Give LanguageOption a `cliFlag` alongside `templateTag` so it mirrors
  UseCaseOption's shape.

Refs #1236

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: David Hanuš <david.hanus@apify.com>
The committed #14 suggestion used `matchesUseCase` (a function) in the
guard condition — TS2774 build failure. Use the `hasUseCaseFilter`
boolean so the exact-match tier is skipped only when no filter is
active, letting the show-all path lead with curated templates.

Refs #1236

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@l2ysho
l2ysho requested a review from DaveHanns July 28, 2026 21:20
Comment thread src/commands/create.ts

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggestion: the static override interactiveNote should be updated.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggestion: the static override examples should be updated.

Comment thread src/lib/create-utils.ts Outdated
...USE_CASE_OPTIONS.map((option) => ({ name: option.label, value: option.templateTag })),
new Separator(),
// "Skip (show all)" maps to the marker the algorithm reads as "no use-case filter".
{ name: 'Skip (show all)', value: ANY_TEMPLATE_USE_CASE },

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggestion: lets unify the copy with the language. Either both Skip (...) or both Any ... I lean towards the later, Any ...

@DaveHanns DaveHanns left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Few comments, otherwise LGTM.

The main things are the drop of other option of --language flag and unification of "skip / no preference / any" language and use case.

l2ysho and others added 3 commits July 29, 2026 15:23
…copy

Update `interactiveNote` and `examples` to describe the use-case/language
wizard and the -u/-l filter flags, and unify the use-case prompt's "skip"
copy with the language prompt ("Any use case" ↔ "Any language").

Addresses PR #1278 review comments r3672018996, r3672030798, r3672045202.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The use-case prompt label changed to "Any use case"; update the
getTemplateRecommendation doc comment to match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@l2ysho
l2ysho requested review from DaveHanns and szaganek July 29, 2026 13:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

t-builders Issues owned by the Builders team. tested Temporary label used only programatically for some analytics.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Guided apify create wizard: use-case → language → best-match template

4 participants