Skip to content

Commit 84647b9

Browse files
l2yshoclaudeszaganekDaveHanns
authored
feat(create): guided wizard with use-case and language filters (#1278)
Closes #1236. **TL;DR** — `apify create` becomes a guided wizard: **use case → language → best-match template**, plus `apify templates ls` for discovery. Local scaffold only; the git-sourced flow is #1237 and `--json`/`--origin` is #1238. ## What changed - **Wizard** — asks _"What do you want to build?"_ then the language, then shows one scrollable, fit-ranked template list: exact matches first (top preselected), a separator, then the closest alternatives. - **New flags** `-u/--use-case`, `-l/--language`. `-t/--template` stays authoritative and skips the wizard; `-u`/`-l` are ignored when it's passed. - **Language aliases** `js` / `ts` / `py`. No `other` option — every template has a language. - **New command** `apify templates ls [--json]`; `--json` emits the 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 tag | | `--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** (`ANY_TEMPLATE_USE_CASE` / `ANY_TEMPLATE_LANGUAGE` — symmetric by design). ## Tests Unit tests for `getTemplateRecommendation` (tiers, dedup, any-language, no-exact), `buildTemplateChoiceList` (separator, labels, hint), and the flag→tag mappings; `create` covers `--template` precedence. `build` / `lint` / `format` clean, `docs/` regenerated, wizard driven end-to-end in a terminal. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Edyta <142720610+szaganek@users.noreply.github.com> Co-authored-by: David Hanuš <david.hanus@apify.com>
1 parent 964a9e8 commit 84647b9

14 files changed

Lines changed: 831 additions & 68 deletions

docs/reference.md

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -277,13 +277,20 @@ DESCRIPTION
277277
directory.
278278
279279
USAGE
280-
$ apify create [actorName] [--omit-optional-deps]
281-
[--skip-dependency-install] [--skip-git-init] [-t <value>]
280+
$ apify create [actorName]
281+
[-l javascript|js|typescript|ts|python|py]
282+
[--omit-optional-deps] [--skip-dependency-install]
283+
[--skip-git-init] [-t <value>]
284+
[-u web-scraper|ai-agent|data-pipeline|browser-automation]
282285
283286
ARGUMENTS
284287
actorName Name of the Actor and its directory.
285288
286289
FLAGS
290+
-l, --language=<option> Filter templates by
291+
programming language. Ignored when --template is
292+
provided.
293+
<options: javascript|js|typescript|ts|python|py>
287294
--omit-optional-deps Skip installing optional
288295
dependencies.
289296
--skip-dependency-install Skip installing Actor
@@ -295,6 +302,37 @@ FLAGS
295302
it. Visit
296303
https://raw.githubusercontent.com/apify/actor-templates/master/templates/manifest.json
297304
to find available template names.
305+
-u, --use-case=<option> Filter templates by
306+
use case. Ignored when --template is provided. To
307+
see the use cases each template supports, run
308+
"apify templates ls".
309+
<options:
310+
web-scraper|ai-agent|data-pipeline|browser-automation>
311+
```
312+
313+
##### `apify templates`
314+
315+
```sh
316+
DESCRIPTION
317+
Explore the Actor templates used by "apify create".
318+
319+
SUBCOMMANDS
320+
templates ls Prints all available Actor templates, including the
321+
use cases and language each one supports.
322+
```
323+
324+
##### `apify templates ls`
325+
326+
```sh
327+
DESCRIPTION
328+
Prints all available Actor templates, including the use cases and language
329+
each one supports.
330+
331+
USAGE
332+
$ apify templates ls [--json]
333+
334+
FLAGS
335+
--json Format the command output as JSON.
298336
```
299337
300338
##### `apify init`

scripts/generate-cli-docs.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ const categories: Record<string, CommandsInCategory[]> = {
2121
'actor-dev': [
2222
//
2323
{ command: Commands.create },
24+
{ command: Commands.templates },
25+
{ command: Commands.templatesLs },
2426
{ command: Commands.init },
2527
{ command: Commands.run },
2628
{ command: Commands.validateSchema },

src/commands/_register.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import { RunsIndexCommand } from './runs/_index.js';
3434
import { SecretsIndexCommand } from './secrets/_index.js';
3535
import { TasksIndexCommand } from './task/_index.js';
3636
import { TelemetryIndexCommand } from './telemetry/_index.js';
37+
import { TemplatesIndexCommand } from './templates/_index.js';
3738
import { ValidateSchemaCommand } from './validate-schema.js';
3839

3940
export const apifyCommands = [
@@ -50,6 +51,7 @@ export const apifyCommands = [
5051
SecretsIndexCommand,
5152
TasksIndexCommand,
5253
TelemetryIndexCommand,
54+
TemplatesIndexCommand,
5355

5456
// top-level
5557
ApiCommand,

src/commands/create.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import { getInstallCommandSuggestion } from '../lib/hooks/runtimes/utils.js';
3030
import { ProjectLanguage, useCwdProject } from '../lib/hooks/useCwdProject.js';
3131
import { createPrefilledInputFileFromInputSchema } from '../lib/input_schema.js';
3232
import { error, info, simpleLog, success, warning } from '../lib/outputs.js';
33+
import { LANGUAGE_FLAG_CHOICES, USE_CASE_FLAG_CHOICES } from '../lib/templates/consts.js';
3334
import {
3435
downloadAndUnzip,
3536
getJsonFileContent,
@@ -50,13 +51,17 @@ export class CreateCommand extends ApifyCommand<typeof CreateCommand> {
5051
static override interactive = true;
5152

5253
static override interactiveNote =
53-
'Prompts for an Actor name and template if not provided. To run non-interactively, pass the name as a positional argument and --template.';
54+
'Prompts for an Actor name, then guides you through what you want to build, a language, and a template when they are not provided. To run non-interactively, pass the name and --template. Use --use-case and --language to narrow the template list.';
5455

5556
static override examples = [
5657
{
57-
description: 'Create a new Actor project interactively (prompts for name and template).',
58+
description: 'Create a new Actor project interactively (guided name, use case, language, and template prompts).',
5859
command: 'apify create',
5960
},
61+
{
62+
description: 'Narrow the guided template list by use case and language.',
63+
command: 'apify create my-actor --use-case web-scraper --language python',
64+
},
6065
{
6166
description: 'Create non-interactively with explicit name and template.',
6267
command: 'apify create my-actor --template js-crawlee-cheerio',
@@ -75,6 +80,19 @@ export class CreateCommand extends ApifyCommand<typeof CreateCommand> {
7580
description: `Template for the Actor. If not provided, the command will prompt for it. Visit ${manifestUrl} to find available template names.`,
7681
required: false,
7782
}),
83+
'use-case': Flags.string({
84+
char: 'u',
85+
description:
86+
'Filter templates by use case. Ignored when --template is provided. To see the use cases each template supports, run "apify templates ls".',
87+
choices: USE_CASE_FLAG_CHOICES,
88+
required: false,
89+
}),
90+
language: Flags.string({
91+
char: 'l',
92+
description: 'Filter templates by programming language. Ignored when --template is provided.',
93+
choices: LANGUAGE_FLAG_CHOICES,
94+
required: false,
95+
}),
7896
'skip-dependency-install': Flags.boolean({
7997
description: 'Skip installing Actor dependencies.',
8098
required: false,
@@ -104,7 +122,7 @@ export class CreateCommand extends ApifyCommand<typeof CreateCommand> {
104122

105123
async run() {
106124
let { actorName } = this.args;
107-
const { template: templateName, skipDependencyInstall, skipGitInit } = this.flags;
125+
const { template: templateName, useCase, language, skipDependencyInstall, skipGitInit } = this.flags;
108126

109127
// --template-archive-url is an internal, undocumented flag that's used
110128
// for testing of templates that are not yet published in the manifest
@@ -157,7 +175,7 @@ export class CreateCommand extends ApifyCommand<typeof CreateCommand> {
157175
};
158176

159177
if (!templateArchiveUrl) {
160-
const templateDefinition = await getTemplateDefinition(templateName, manifestPromise);
178+
const templateDefinition = await getTemplateDefinition(templateName, manifestPromise, { useCase, language });
161179
({ archiveUrl: templateArchiveUrl, messages } = templateDefinition);
162180
this.telemetryData.create.templateId = templateDefinition.id;
163181
this.telemetryData.create.templateName = templateDefinition.name;

src/commands/templates/_index.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { ApifyCommand } from '../../lib/command-framework/apify-command.js';
2+
import { TemplatesLsCommand } from './ls.js';
3+
4+
export class TemplatesIndexCommand extends ApifyCommand<typeof TemplatesIndexCommand> {
5+
static override name = 'templates' as const;
6+
7+
static override description = 'Explore the Actor templates used by "apify create".';
8+
9+
static override group = 'Local Actor Development';
10+
11+
static override subcommands = [TemplatesLsCommand];
12+
13+
async run() {
14+
this.printHelp();
15+
}
16+
}

src/commands/templates/ls.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { fetchManifest } from '@apify/actor-templates';
2+
3+
import { ApifyCommand } from '../../lib/command-framework/apify-command.js';
4+
import { CompactMode, ResponsiveTable } from '../../lib/commands/responsive-table.js';
5+
import { info, simpleLog } from '../../lib/outputs.js';
6+
import { printJsonToStdout } from '../../lib/utils.js';
7+
8+
const table = new ResponsiveTable({
9+
allColumns: ['Template', 'Label', 'Language', 'Use cases'],
10+
mandatoryColumns: ['Template', 'Language', 'Use cases'],
11+
});
12+
13+
export class TemplatesLsCommand extends ApifyCommand<typeof TemplatesLsCommand> {
14+
static override name = 'ls' as const;
15+
16+
static override description =
17+
'Prints all available Actor templates, including the use cases and language each one supports.';
18+
19+
static override examples = [
20+
{
21+
description: 'List all available templates.',
22+
command: 'apify templates ls',
23+
},
24+
{
25+
description: 'List templates as JSON (includes the use-case tags for scripting).',
26+
command: 'apify templates ls --json',
27+
},
28+
];
29+
30+
static override enableJsonFlag = true;
31+
32+
async run() {
33+
const { json } = this.flags;
34+
35+
const manifest = await fetchManifest().catch((err) => {
36+
throw new Error(`Could not fetch template list from server. Cause: ${(err as Error)?.message}`);
37+
});
38+
39+
if (json) {
40+
printJsonToStdout(manifest.templates);
41+
return;
42+
}
43+
44+
if (manifest.templates.length === 0) {
45+
info({ message: 'There are no templates available.', stdout: true });
46+
return;
47+
}
48+
49+
for (const template of manifest.templates) {
50+
table.pushRow({
51+
Template: template.name,
52+
Label: template.label,
53+
Language: template.category,
54+
'Use cases': (template.useCases ?? []).join(', '),
55+
});
56+
}
57+
58+
simpleLog({
59+
message: table.render(CompactMode.WebLikeCompact),
60+
stdout: true,
61+
});
62+
}
63+
}

0 commit comments

Comments
 (0)