feat(create): guided wizard with use-case and language filters - #1278
feat(create): guided wizard with use-case and language filters#1278l2ysho wants to merge 14 commits into
Conversation
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>
- 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>
…anguage-best-match-template
| USAGE | ||
| $ apify create [actorName] [--omit-optional-deps] | ||
| $ apify create [actorName] | ||
| [-l javascript|typescript|python|other] [--omit-optional-deps] |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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).
| $ 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] |
There was a problem hiding this comment.
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? 🤔
| 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; |
There was a problem hiding this comment.
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;
| /** kebab-case value accepted by the `--use-case` flag. */ | ||
| flag: string; | ||
| /** Manifest id this maps to (value in `useCases[]`). */ | ||
| id: string; |
There was a problem hiding this comment.
Nit: a bit clearer naming might be beneficial:
| /** 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; |
There was a problem hiding this comment.
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
| /** Manifest `category`, which is also the value accepted by the `--language` flag. */ | ||
| id: string; |
There was a problem hiding this comment.
Nit: same as above, templateTag might be a bit clearer.
There was a problem hiding this comment.
Same as above, the type here might be more specific by deriving from the USE_CASE_OPTIONS
There was a problem hiding this comment.
Nit: also consider to swallow the dusplicity and have both cliFlag and templateTag here even tho they are the same.
There was a problem hiding this comment.
FYI, a consolidation epic to reduce copied-over code in CLI:
#1308
Hopefully we will get to it one day :D
| const hasLanguageFilter = languageId !== ANY_TEMPLATE_LANGUAGE; | ||
| const hasUseCaseFilter = useCaseId !== undefined; |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
Super nit:
| const matchesUseCase = (template: Template) => !hasUseCaseFilter || (template.useCases?.includes(useCaseId) ?? false); | |
| const matchesUseCase = (template: Template) => !hasUseCaseFilter || !!template.useCases?.includes(useCaseId); |
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>
…anguage-best-match-template
- 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>
There was a problem hiding this comment.
Suggestion: the static override interactiveNote should be updated.
There was a problem hiding this comment.
Suggestion: the static override examples should be updated.
| ...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 }, |
There was a problem hiding this comment.
Suggestion: lets unify the copy with the language. Either both Skip (...) or both Any ... I lean towards the later, Any ...
DaveHanns
left a comment
There was a problem hiding this comment.
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.
…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>
…anguage-best-match-template
Closes #1236.
Reshapes
apify createinto the guided wizard: use case → language → best-match template, plus a newapify templates lscommand. Local scaffold only (--source apify).What changed
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.-u/--use-case,-l/--language.-t/--templatestays authoritative and bypasses the wizard;-u/-lare ignored when-tis given.apify templates ls [--json]—--jsonemits full template objects includinguseCases[].getTemplateRecommendationfrom apify-core with two CLI changes: per-templateisExactMatchand no result limit.Flag → manifest mapping
--use-case--language(+ alias)categoryweb-scraperWEB_SCRAPINGjavascript/jsjavascriptai-agentAItypescript/tstypescriptdata-pipelineINTEGRATIONpython/pypythonbrowser-automationAUTOMATION"Any use case" and "Any language" both mean no filter.
Test plan
getTemplateRecommendation(tiers, dedup, any-language, skip-use-case, no-exact) andbuildTemplateChoiceList(separator, labels, hint).build/lint/formatclean;docs/regenerated.🤖 Generated with Claude Code