create-warlock 5.11.0 → 5.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/CHANGELOG.md +25 -3
  2. package/README.md +38 -5
  3. package/esm/commands/create-new-app/index.mjs +102 -9
  4. package/esm/commands/create-new-app/index.mjs.map +1 -1
  5. package/esm/commands/create-new-app/types.d.mts +8 -3
  6. package/esm/commands/create-warlock-app/index.mjs +12 -1
  7. package/esm/commands/create-warlock-app/index.mjs.map +1 -1
  8. package/esm/features/agent-kit-targets.mjs +134 -0
  9. package/esm/features/agent-kit-targets.mjs.map +1 -0
  10. package/esm/features/features-map.mjs +18 -0
  11. package/esm/features/features-map.mjs.map +1 -1
  12. package/esm/flags/definitions.mjs +89 -0
  13. package/esm/flags/definitions.mjs.map +1 -0
  14. package/esm/helpers/app.mjs +21 -0
  15. package/esm/helpers/app.mjs.map +1 -1
  16. package/esm/index.mjs +15 -23
  17. package/esm/index.mjs.map +1 -1
  18. package/esm/prompts/ask-stack.mjs +35 -0
  19. package/esm/prompts/ask-stack.mjs.map +1 -0
  20. package/esm/summary.mjs +25 -0
  21. package/esm/summary.mjs.map +1 -0
  22. package/llms-full.txt +65 -38
  23. package/llms.txt +1 -1
  24. package/package.json +2 -2
  25. package/skills/create-a-warlock-project/SKILL.md +65 -38
  26. package/templates/warlock/AGENTS.md +1 -1
  27. package/templates/warlock/skills/api-design/SKILL.md +2 -2
  28. package/templates/warlock/skills/security-baseline/SKILL.md +1 -1
  29. package/templates/warlock/src/app/auth/controllers/logout-all.controller.ts +1 -1
  30. package/templates/warlock/src/app/auth/controllers/logout.controller.ts +3 -3
  31. package/templates/warlock/src/app/auth/controllers/me.controller.ts +1 -1
  32. package/templates/warlock/src/app/auth/requests/guarded.request.ts +2 -2
  33. package/templates/warlock/src/app/posts/controllers/create-new-post.controller.ts +1 -1
  34. package/templates/warlock/src/app/users/models/user/migrations/11-12-2025_23-58-03-user.migration.ts +1 -0
  35. package/templates/warlock/src/app/users/models/user/user.model.ts +12 -4
  36. package/templates/warlock/src/app/users/services/login-social.ts +4 -3
  37. package/templates/warlock/src/config/database.postgres.ts +4 -0
  38. package/templates/warlock/src/typings.d.ts +14 -8
  39. package/templates/warlock/src/web/home/components/contact-section.tsx +31 -14
  40. package/templates/warlock/src/web/home/hooks/use-contact-form.ts +18 -7
  41. package/templates/warlock/src/web/home/index.page.tsx +1 -1
  42. package/templates/warlock/src/web/root.tsx +5 -2
  43. package/templates/warlock/src/web/home/hooks/use-locale-switcher.ts +0 -35
@@ -0,0 +1,35 @@
1
+ import { cancel, isCancel, select } from "@clack/prompts";
2
+
3
+ //#region ../create-warlock/src/prompts/ask-stack.ts
4
+ /**
5
+ * The ONE structural question the default (non-`--interactive`) interactive
6
+ * path is allowed to ask: does the generated app need server-rendered web
7
+ * pages, or is it an API only? This is the sole fork that changes the shape
8
+ * of the generated project — every other choice (database, features,
9
+ * package manager, agent-kit targets, …) is reversible within minutes and
10
+ * ships as a flag with a default instead.
11
+ */
12
+ async function askStack() {
13
+ const answer = await select({
14
+ message: "What are we building?",
15
+ options: [{
16
+ value: "api",
17
+ label: "API only",
18
+ hint: "A REST/GraphQL API, no server-rendered pages"
19
+ }, {
20
+ value: "web",
21
+ label: "Full-stack web",
22
+ hint: "API + server-rendered React pages (SSR)"
23
+ }],
24
+ initialValue: "api"
25
+ });
26
+ if (isCancel(answer)) {
27
+ cancel("Setup cancelled");
28
+ process.exit(0);
29
+ }
30
+ return answer;
31
+ }
32
+
33
+ //#endregion
34
+ export { askStack };
35
+ //# sourceMappingURL=ask-stack.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ask-stack.mjs","names":[],"sources":["../../../../../../create-warlock/src/prompts/ask-stack.ts"],"sourcesContent":["import { cancel, isCancel, select } from \"@clack/prompts\";\nimport type { Stack } from \"../commands/create-new-app/types\";\n\n/**\n * The ONE structural question the default (non-`--interactive`) interactive\n * path is allowed to ask: does the generated app need server-rendered web\n * pages, or is it an API only? This is the sole fork that changes the shape\n * of the generated project — every other choice (database, features,\n * package manager, agent-kit targets, …) is reversible within minutes and\n * ships as a flag with a default instead.\n */\nexport async function askStack(): Promise<Stack> {\n const answer = await select({\n message: \"What are we building?\",\n options: [\n {\n value: \"api\",\n label: \"API only\",\n hint: \"A REST/GraphQL API, no server-rendered pages\",\n },\n {\n value: \"web\",\n label: \"Full-stack web\",\n hint: \"API + server-rendered React pages (SSR)\",\n },\n ],\n initialValue: \"api\",\n });\n\n if (isCancel(answer)) {\n cancel(\"Setup cancelled\");\n process.exit(0);\n }\n\n return answer as Stack;\n}\n"],"mappings":";;;;;;;;;;;AAWA,eAAsB,WAA2B;CAC/C,MAAM,SAAS,MAAM,OAAO;EAC1B,SAAS;EACT,SAAS,CACP;GACE,OAAO;GACP,OAAO;GACP,MAAM;EACR,GACA;GACE,OAAO;GACP,OAAO;GACP,MAAM;EACR,CACF;EACA,cAAc;CAChB,CAAC;CAED,IAAI,SAAS,MAAM,GAAG;EACpB,OAAO,iBAAiB;EACxB,QAAQ,KAAK,CAAC;CAChB;CAEA,OAAO;AACT"}
@@ -0,0 +1,25 @@
1
+ import { getDatabaseLabel } from "./features/database-drivers.mjs";
2
+ import { colors } from "@mongez/copper";
3
+
4
+ //#region ../create-warlock/src/summary.ts
5
+ /**
6
+ * Print what the scaffolder decided on the user's behalf, and how to change
7
+ * each decision — required because almost every choice is now a default
8
+ * rather than a prompt, so this is the only place a user sees them.
9
+ */
10
+ function printDecisionsSummary({ packageManager, options }) {
11
+ const databaseLabel = getDatabaseLabel(options.databaseDriver);
12
+ const headline = [
13
+ `Using ${packageManager}`,
14
+ `database: ${databaseLabel}`,
15
+ `agent-kit targets: ${options.agents.join(", ")}`
16
+ ].join(" · ");
17
+ console.log();
18
+ console.log(colors.cyan(headline));
19
+ console.log(colors.dim(" change these — package manager: --pm, database: --db/--no-db, agents: --agents, features: --features, ai: --ai, git: --git/--no-git, jwt: --jwt/--no-jwt — or re-run with --interactive for the full wizard. Package-manager-level choices (husky, lint config, etc.) can also be edited directly in the generated project."));
20
+ console.log();
21
+ }
22
+
23
+ //#endregion
24
+ export { printDecisionsSummary };
25
+ //# sourceMappingURL=summary.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"summary.mjs","names":[],"sources":["../../../../../create-warlock/src/summary.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport { getDatabaseLabel } from \"./features/database-drivers\";\nimport { AppOptions } from \"./commands/create-new-app/types\";\n\n/** What decisions.spec.ts / the terminal actually needs to render the summary. */\nexport type Decisions = {\n packageManager: string;\n options: AppOptions;\n};\n\n/**\n * Print what the scaffolder decided on the user's behalf, and how to change\n * each decision — required because almost every choice is now a default\n * rather than a prompt, so this is the only place a user sees them.\n */\nexport function printDecisionsSummary({ packageManager, options }: Decisions): void {\n const databaseLabel = getDatabaseLabel(options.databaseDriver);\n\n const headline = [\n `Using ${packageManager}`,\n `database: ${databaseLabel}`,\n `agent-kit targets: ${options.agents.join(\", \")}`,\n ].join(\" · \");\n\n console.log();\n console.log(colors.cyan(headline));\n console.log(\n colors.dim(\n \" change these — package manager: --pm, database: --db/--no-db, agents: --agents, \" +\n \"features: --features, ai: --ai, git: --git/--no-git, jwt: --jwt/--no-jwt — \" +\n \"or re-run with --interactive for the full wizard. Package-manager-level choices \" +\n \"(husky, lint config, etc.) can also be edited directly in the generated project.\",\n ),\n );\n console.log();\n}\n"],"mappings":";;;;;;;;;AAeA,SAAgB,sBAAsB,EAAE,gBAAgB,WAA4B;CAClF,MAAM,gBAAgB,iBAAiB,QAAQ,cAAc;CAE7D,MAAM,WAAW;EACf,SAAS;EACT,aAAa;EACb,sBAAsB,QAAQ,OAAO,KAAK,IAAI;CAChD,CAAC,CAAC,KAAK,KAAK;CAEZ,QAAQ,IAAI;CACZ,QAAQ,IAAI,OAAO,KAAK,QAAQ,CAAC;CACjC,QAAQ,IACN,OAAO,IACL,+TAIF,CACF;CACA,QAAQ,IAAI;AACd"}
package/llms-full.txt CHANGED
@@ -8,52 +8,48 @@
8
8
 
9
9
  ---
10
10
  name: create-a-warlock-project
11
- description: 'Scaffold a brand-new Warlock.js project with `create-warlock` — the interactive wizard and its non-interactive flag set. A single `--yes` command scaffolds the entire app from flags. Every flag (`--name`, `--db`, `--pm`, `--features`, `--ai`, `--git/--no-git`, `--jwt/--no-jwt`, `--no-db`, `--yes`), every valid database-driver / feature / AI-provider key, opting out of a database (`--db=none` / `--no-db`, which removes `src/config/database.ts`), and the directory tree the template emits. Triggers: `create-warlock`, `npm create warlock`, `npx create-warlock`; "scaffold a warlock app", "start a new warlock project", "create-warlock flags", "non-interactive warlock scaffold", "scaffold without a database", "which features can I pass to create-warlock". Skip: running an already-created project (`@warlock.js/core/run-app/SKILL.md`); adding a feature to an existing project (`warlock add`, `@warlock.js/core/add-connector/SKILL.md`); generating a module inside a project (`@warlock.js/core/create-module/SKILL.md`).'
11
+ description: 'Scaffold a brand-new Warlock.js project with `create-warlock` — fully headless by default (at most one prompt — API-only vs full-stack web) plus `--interactive`/`--customize` for the full long-form wizard. Every flag (`--name`, `--stack`, `--db`, `--pm`, `--features`, `--ai`, `--agents`, `--git/--no-git`, `--jwt/--no-jwt`, `--no-db`, `--yes`, `--interactive`), every valid database-driver / feature / AI-provider / agent-kit-target key, opting out of a database (`--db=none` / `--no-db`, which removes `src/config/database.ts`), the post-scaffold decisions summary, and the directory tree the template emits. Triggers: `create-warlock`, `npm create warlock`, `npx create-warlock`; "scaffold a warlock app", "start a new warlock project", "create-warlock flags", "non-interactive warlock scaffold", "scaffold without a database", "which features can I pass to create-warlock", "agent-kit targets". Skip: running an already-created project (`@warlock.js/core/run-app/SKILL.md`); adding a feature to an existing project (`warlock add`, `@warlock.js/core/add-connector/SKILL.md`); generating a module inside a project (`@warlock.js/core/create-module/SKILL.md`).'
12
12
  ---
13
13
 
14
14
  # create-warlock — scaffold a new project
15
15
 
16
- `create-warlock` builds a fresh Warlock.js project: it copies the `warlock` template, substitutes the project name, wires the chosen database driver into `.env` (or, when you opt out with `--db=none`, removes `src/config/database.ts`), then delegates every optional package install to the project's own `warlock add` so dependency versions are never duplicated by the scaffolder.
16
+ `create-warlock` builds a fresh Warlock.js project: it copies the `warlock` template, substitutes the project name, wires the chosen database driver into `.env` (or, when you opt out with `--db=none`, removes `src/config/database.ts`), derives `agent-kit` targets into the generated project, then delegates every optional package install to the project's own `warlock add` so dependency versions are never duplicated by the scaffolder.
17
17
 
18
18
  ## The shape
19
19
 
20
20
  ```bash
21
- # Interactive wizard (recommended for humans)
22
- npm create warlock@latest
23
- # or: npx create-warlock
21
+ # Default: at most ONE prompt (API-only vs full-stack web) — everything else is a flag/default
22
+ npx create-warlock my-app
24
23
 
25
- # Non-interactive (CI, agents, reproducible setups) — one command scaffolds the whole app
24
+ # Fully headless — one command scaffolds the whole app, no prompts at all
26
25
  npx create-warlock my-app --yes --db=postgres --features=test,redis --ai=ai-openai
27
26
 
27
+ # The one structural fork
28
+ npx create-warlock my-web-app --yes --stack=web
29
+
28
30
  # No database at all
29
31
  npx create-warlock my-api --yes --no-db --features=test
32
+
33
+ # The full long-form wizard, one prompt at a time
34
+ npx create-warlock --interactive
30
35
  ```
31
36
 
32
37
  The first positional argument is the project name; it also becomes the directory created under the current working directory. If that directory already exists, the command aborts — it never overwrites.
33
38
 
34
- ## Interactive flow
35
-
36
- The wizard asks, in order:
37
-
38
- 1. **Project name** — required.
39
- 2. **Package manager** — chosen from the managers detected on the system (npm is always offered; yarn / pnpm appear if installed).
40
- 3. **Database driver** — a single select: a driver, or **None** to scaffold without a database (see keys below).
41
- 4. **Features** — a multiselect of optional packages (`react` is pre-checked).
42
- 5. **AI providers** — a multiselect; picking any one pulls `@warlock.js/ai` automatically.
43
- 6. **Initialize Git?** — yes/no.
44
- 7. **Generate JWT secret keys?** — yes/no. If no, the cache is warmed instead.
39
+ ## Three modes
45
40
 
46
- ## Non-interactive flags
47
-
48
- Pass `--yes` (or `-y`) to skip every prompt and build from flags with defaults.
41
+ 1. **Default (a TTY, no `--yes`, no `--interactive`)** — asks the project name (if not already given) plus AT MOST ONE structural question: API-only or full-stack web (`--stack`). This is the only choice that changes the generated app's shape; every other choice below is reversible within minutes of scaffolding, so it is a flag with a sensible default, never a prompt.
42
+ 2. **`--yes` (or no TTY at all — CI, a script, an agent harness)** — fully headless. Every answer comes from a flag or its default; nothing is ever prompted, and stdin is never read.
43
+ 3. **`--interactive` (alias `--customize`)** — the full long-form wizard: project name, package manager, database driver, features, AI providers, git, JWT — one prompt after another, exactly like earlier `create-warlock` releases.
49
44
 
50
45
  **Without a terminal, `--yes` is not required.** When stdin is not a TTY — CI, a
51
- script, a container, an agent harness — the scaffolder does not try to prompt.
52
- If the flags already answer everything it needs, it simply scaffolds. Prompting
53
- is the fallback, not the precondition.
46
+ script, a container, an agent harness — the scaffolder does not try to prompt,
47
+ regardless of which of the three modes was requested. If the flags already
48
+ answer everything it needs, it simply scaffolds. Prompting is the fallback,
49
+ not the precondition.
54
50
 
55
51
  Only a question that genuinely cannot be answered stops the run, and in practice
56
- that is just the project name, the one prompt with no default:
52
+ that is just the project name, the one required answer with no default:
57
53
 
58
54
  ```
59
55
  A project name is required and no terminal is available to ask for one. Pass it as
@@ -61,21 +57,51 @@ the first argument or --name=<name>. Non-interactive flags: --yes, --pm=<npm|yar
61
57
  --db=<driver>|--no-db, --features=<list>, --ai=<list>, --git|--no-git, --jwt|--no-jwt.
62
58
  ```
63
59
 
64
- | Flag | Takes value | Default | Purpose |
65
- | ------------------------ | ----------- | -------------- | ------------------------------------------------------------------- |
66
- | `<positional>` / `--name`| yes | — (required) | Project name + target directory. |
67
- | `--db` | yes | `mongodb` | Database driver key (`mongodb`, `postgres`, or `none`). |
68
- | `--no-db` | no | — | Opt out of a database — shorthand for `--db=none`. |
69
- | `--pm` | yes | auto-detected | Package manager — must be one of `npm` / `yarn` / `pnpm` / `bun`; anything else is rejected before scaffolding starts. |
70
- | `--features` | yes (CSV) | none | Comma-separated optional feature keys. |
71
- | `--ai` | yes (CSV) | none | Comma-separated AI provider keys (auto-pulls `@warlock.js/ai`). |
72
- | `--git` / `--no-git` | no | off | Initialize a Git repo (`git init` + `main` branch + initial commit).|
73
- | `--jwt` / `--no-jwt` | no | off | Generate JWT secrets via the project's `jwt` script. |
74
- | `--yes` / `-y` | no | off | Non-interactive mode. |
60
+ ## After scaffolding: the decisions summary
61
+
62
+ Because almost every choice defaults silently, `create-warlock` prints what it
63
+ decided right after scaffolding, e.g.:
75
64
 
76
- Value flags accept either `--db=postgres` or `--db postgres`. Unknown `--features` / `--ai` keys fail fast before any install.
65
+ ```
66
+ Using npm · database: none · agent-kit targets: claude
67
+ change these — package manager: --pm, database: --db/--no-db, agents: --agents,
68
+ features: --features, ai: --ai, git: --git/--no-git, jwt: --jwt/--no-jwt — or
69
+ re-run with --interactive for the full wizard.
70
+ ```
77
71
 
78
- `--help`/`-h` and `--version`/`-v` short-circuit before anything else — no prompt, filesystem write, or network call, and `--help` wins even over a positional project name. `--version` prints the installed `create-warlock` version.
72
+ ## Flags
73
+
74
+ | Flag | Takes value | Default | Purpose |
75
+ | --------------------------- | ----------- | -------------- | ------------------------------------------------------------------- |
76
+ | `<positional>` / `--name` | yes | — (required) | Project name + target directory. |
77
+ | `--stack` | yes | `api` | `api` or `web` — the one structural question; `web` seeds the `web` feature by default. |
78
+ | `--db` | yes | `mongodb` | Database driver key (`mongodb`, `postgres`, or `none`). |
79
+ | `--no-db` | no | — | Opt out of a database — shorthand for `--db=none`. |
80
+ | `--pm` | yes | inferred (`npm_config_user_agent` / system) | Package manager — must be one of `npm` / `yarn` / `pnpm` / `bun`; anything else is rejected before scaffolding starts. |
81
+ | `--features` | yes (CSV) | none, or `["web"]` when `--stack=web` and `--features` is not given | Comma-separated optional feature keys. An explicit `--features` always wins over the `--stack` default. |
82
+ | `--ai` | yes (CSV) | none | Comma-separated AI provider keys (auto-pulls `@warlock.js/ai`). |
83
+ | `--agents` | yes (CSV) | `claude` | Comma-separated `agent-kit` targets — see below. |
84
+ | `--git` / `--no-git` | no | off | Initialize a Git repo (`git init` + `main` branch + initial commit).|
85
+ | `--jwt` / `--no-jwt` | no | off | Generate JWT secrets via the project's `jwt` script. |
86
+ | `--yes` / `-y` | no | off | Fully headless mode. |
87
+ | `--interactive`, `--customize` | no | off | Restore the full long-form wizard. |
88
+
89
+ Value flags accept either `--db=postgres` or `--db postgres`. Unknown `--features` / `--ai` / `--agents` keys fail fast before any install.
90
+
91
+ `--help`/`-h` and `--version`/`-v` short-circuit before anything else — no prompt, filesystem write, or network call, and `--help` wins even over a positional project name. `--version` prints the installed `create-warlock` version. `--help` lists every flag above together with its default.
92
+
93
+ ## `--agents` and agent-kit targets
94
+
95
+ `--agents` picks which `agent-kit` targets the generated project derives
96
+ docs/skills for (written into the scaffolded `package.json`'s `agentKit.targets`
97
+ when it differs from the default). The default, `claude`, is a tiny built-in
98
+ fallback that always works — even fully offline. Any other target
99
+ (`cursor`, `codex`, `kiro`, `copilot`, `antigravity`, `opencode`, `amp`,
100
+ `goose`, …) is validated against the list published at
101
+ https://mongez.js.org/agent-kit/llms.txt, fetched once and cached locally. If
102
+ that fetch fails for an explicitly requested non-default target, the run fails
103
+ visibly and names every target it *could* determine — it never silently
104
+ guesses.
79
105
 
80
106
  ## Valid keys
81
107
 
@@ -118,11 +144,12 @@ The template ships a working auth module (login / logout / logout-all / refresh
118
144
 
119
145
  ## Setup steps the scaffolder runs
120
146
 
121
- 1. Copy template → substitute name → wire `DB_DRIVER` / `DB_PORT` into `.env` (or, for `--db=none`, remove `src/config/database.ts`).
147
+ 1. Copy template → substitute name → wire `DB_DRIVER` / `DB_PORT` into `.env` (or, for `--db=none`, remove `src/config/database.ts`) → record non-default `--agents` targets into `package.json`'s `agentKit.targets`.
122
148
  2. Install base dependencies (so the `warlock` binary exists).
123
149
  3. `warlock add <driver> <features...> <ai...> --no-install`, then one batched install. The `<driver>` is omitted for `--db=none`.
124
150
  4. `git init` (if `--git`).
125
151
  5. `jwt` script (if `--jwt`) — otherwise `warlock --warm-cache`.
152
+ 6. Print the decisions summary.
126
153
 
127
154
  Failures surface the error and halt; there is no rollback. After it finishes, `cd my-app` and run the dev server.
128
155
 
package/llms.txt CHANGED
@@ -6,4 +6,4 @@
6
6
 
7
7
  ## Skills
8
8
 
9
- - [create-a-warlock-project](create-warlock/create-a-warlock-project/SKILL.md): Scaffold a brand-new Warlock.js project with `create-warlock` — the interactive wizard and its non-interactive flag set. A single `--yes` command scaffolds the entire app from flags. Every flag (`--name`, `--db`, `--pm`, `--features`, `--ai`, `--git/--no-git`, `--jwt/--no-jwt`, `--no-db`, `--yes`), every valid database-driver / feature / AI-provider key, opting out of a database (`--db=none` / `--no-db`, which removes `src/config/database.ts`), and the directory tree the template emits. Triggers: `create-warlock`, `npm create warlock`, `npx create-warlock`; "scaffold a warlock app", "start a new warlock project", "create-warlock flags", "non-interactive warlock scaffold", "scaffold without a database", "which features can I pass to create-warlock". Skip: running an already-created project (`@warlock.js/core/run-app/SKILL.md`); adding a feature to an existing project (`warlock add`, `@warlock.js/core/add-connector/SKILL.md`); generating a module inside a project (`@warlock.js/core/create-module/SKILL.md`).
9
+ - [create-a-warlock-project](create-warlock/create-a-warlock-project/SKILL.md): Scaffold a brand-new Warlock.js project with `create-warlock` — fully headless by default (at most one prompt — API-only vs full-stack web) plus `--interactive`/`--customize` for the full long-form wizard. Every flag (`--name`, `--stack`, `--db`, `--pm`, `--features`, `--ai`, `--agents`, `--git/--no-git`, `--jwt/--no-jwt`, `--no-db`, `--yes`, `--interactive`), every valid database-driver / feature / AI-provider / agent-kit-target key, opting out of a database (`--db=none` / `--no-db`, which removes `src/config/database.ts`), the post-scaffold decisions summary, and the directory tree the template emits. Triggers: `create-warlock`, `npm create warlock`, `npx create-warlock`; "scaffold a warlock app", "start a new warlock project", "create-warlock flags", "non-interactive warlock scaffold", "scaffold without a database", "which features can I pass to create-warlock", "agent-kit targets". Skip: running an already-created project (`@warlock.js/core/run-app/SKILL.md`); adding a feature to an existing project (`warlock add`, `@warlock.js/core/add-connector/SKILL.md`); generating a module inside a project (`@warlock.js/core/create-module/SKILL.md`).
package/package.json CHANGED
@@ -12,7 +12,7 @@
12
12
  "dependencies": {
13
13
  "@clack/prompts": "^0.7.0",
14
14
  "@mongez/copper": "^2.1.2",
15
- "@warlock.js/fs": "5.11.0",
15
+ "@warlock.js/fs": "5.13.0",
16
16
  "@mongez/reinforcements": "^4.0.1",
17
17
  "cross-spawn": "^7.0.3",
18
18
  "rimraf": "^6.0.1",
@@ -21,7 +21,7 @@
21
21
  "engines": {
22
22
  "node": ">=22.13"
23
23
  },
24
- "version": "5.11.0",
24
+ "version": "5.13.0",
25
25
  "type": "module",
26
26
  "main": "./esm/index.mjs",
27
27
  "module": "./esm/index.mjs",
@@ -1,51 +1,47 @@
1
1
  ---
2
2
  name: create-a-warlock-project
3
- description: 'Scaffold a brand-new Warlock.js project with `create-warlock` — the interactive wizard and its non-interactive flag set. A single `--yes` command scaffolds the entire app from flags. Every flag (`--name`, `--db`, `--pm`, `--features`, `--ai`, `--git/--no-git`, `--jwt/--no-jwt`, `--no-db`, `--yes`), every valid database-driver / feature / AI-provider key, opting out of a database (`--db=none` / `--no-db`, which removes `src/config/database.ts`), and the directory tree the template emits. Triggers: `create-warlock`, `npm create warlock`, `npx create-warlock`; "scaffold a warlock app", "start a new warlock project", "create-warlock flags", "non-interactive warlock scaffold", "scaffold without a database", "which features can I pass to create-warlock". Skip: running an already-created project (`@warlock.js/core/run-app/SKILL.md`); adding a feature to an existing project (`warlock add`, `@warlock.js/core/add-connector/SKILL.md`); generating a module inside a project (`@warlock.js/core/create-module/SKILL.md`).'
3
+ description: 'Scaffold a brand-new Warlock.js project with `create-warlock` — fully headless by default (at most one prompt — API-only vs full-stack web) plus `--interactive`/`--customize` for the full long-form wizard. Every flag (`--name`, `--stack`, `--db`, `--pm`, `--features`, `--ai`, `--agents`, `--git/--no-git`, `--jwt/--no-jwt`, `--no-db`, `--yes`, `--interactive`), every valid database-driver / feature / AI-provider / agent-kit-target key, opting out of a database (`--db=none` / `--no-db`, which removes `src/config/database.ts`), the post-scaffold decisions summary, and the directory tree the template emits. Triggers: `create-warlock`, `npm create warlock`, `npx create-warlock`; "scaffold a warlock app", "start a new warlock project", "create-warlock flags", "non-interactive warlock scaffold", "scaffold without a database", "which features can I pass to create-warlock", "agent-kit targets". Skip: running an already-created project (`@warlock.js/core/run-app/SKILL.md`); adding a feature to an existing project (`warlock add`, `@warlock.js/core/add-connector/SKILL.md`); generating a module inside a project (`@warlock.js/core/create-module/SKILL.md`).'
4
4
  ---
5
5
 
6
6
  # create-warlock — scaffold a new project
7
7
 
8
- `create-warlock` builds a fresh Warlock.js project: it copies the `warlock` template, substitutes the project name, wires the chosen database driver into `.env` (or, when you opt out with `--db=none`, removes `src/config/database.ts`), then delegates every optional package install to the project's own `warlock add` so dependency versions are never duplicated by the scaffolder.
8
+ `create-warlock` builds a fresh Warlock.js project: it copies the `warlock` template, substitutes the project name, wires the chosen database driver into `.env` (or, when you opt out with `--db=none`, removes `src/config/database.ts`), derives `agent-kit` targets into the generated project, then delegates every optional package install to the project's own `warlock add` so dependency versions are never duplicated by the scaffolder.
9
9
 
10
10
  ## The shape
11
11
 
12
12
  ```bash
13
- # Interactive wizard (recommended for humans)
14
- npm create warlock@latest
15
- # or: npx create-warlock
13
+ # Default: at most ONE prompt (API-only vs full-stack web) — everything else is a flag/default
14
+ npx create-warlock my-app
16
15
 
17
- # Non-interactive (CI, agents, reproducible setups) — one command scaffolds the whole app
16
+ # Fully headless — one command scaffolds the whole app, no prompts at all
18
17
  npx create-warlock my-app --yes --db=postgres --features=test,redis --ai=ai-openai
19
18
 
19
+ # The one structural fork
20
+ npx create-warlock my-web-app --yes --stack=web
21
+
20
22
  # No database at all
21
23
  npx create-warlock my-api --yes --no-db --features=test
24
+
25
+ # The full long-form wizard, one prompt at a time
26
+ npx create-warlock --interactive
22
27
  ```
23
28
 
24
29
  The first positional argument is the project name; it also becomes the directory created under the current working directory. If that directory already exists, the command aborts — it never overwrites.
25
30
 
26
- ## Interactive flow
27
-
28
- The wizard asks, in order:
29
-
30
- 1. **Project name** — required.
31
- 2. **Package manager** — chosen from the managers detected on the system (npm is always offered; yarn / pnpm appear if installed).
32
- 3. **Database driver** — a single select: a driver, or **None** to scaffold without a database (see keys below).
33
- 4. **Features** — a multiselect of optional packages (`react` is pre-checked).
34
- 5. **AI providers** — a multiselect; picking any one pulls `@warlock.js/ai` automatically.
35
- 6. **Initialize Git?** — yes/no.
36
- 7. **Generate JWT secret keys?** — yes/no. If no, the cache is warmed instead.
31
+ ## Three modes
37
32
 
38
- ## Non-interactive flags
39
-
40
- Pass `--yes` (or `-y`) to skip every prompt and build from flags with defaults.
33
+ 1. **Default (a TTY, no `--yes`, no `--interactive`)** — asks the project name (if not already given) plus AT MOST ONE structural question: API-only or full-stack web (`--stack`). This is the only choice that changes the generated app's shape; every other choice below is reversible within minutes of scaffolding, so it is a flag with a sensible default, never a prompt.
34
+ 2. **`--yes` (or no TTY at all — CI, a script, an agent harness)** — fully headless. Every answer comes from a flag or its default; nothing is ever prompted, and stdin is never read.
35
+ 3. **`--interactive` (alias `--customize`)** — the full long-form wizard: project name, package manager, database driver, features, AI providers, git, JWT — one prompt after another, exactly like earlier `create-warlock` releases.
41
36
 
42
37
  **Without a terminal, `--yes` is not required.** When stdin is not a TTY — CI, a
43
- script, a container, an agent harness — the scaffolder does not try to prompt.
44
- If the flags already answer everything it needs, it simply scaffolds. Prompting
45
- is the fallback, not the precondition.
38
+ script, a container, an agent harness — the scaffolder does not try to prompt,
39
+ regardless of which of the three modes was requested. If the flags already
40
+ answer everything it needs, it simply scaffolds. Prompting is the fallback,
41
+ not the precondition.
46
42
 
47
43
  Only a question that genuinely cannot be answered stops the run, and in practice
48
- that is just the project name, the one prompt with no default:
44
+ that is just the project name, the one required answer with no default:
49
45
 
50
46
  ```
51
47
  A project name is required and no terminal is available to ask for one. Pass it as
@@ -53,21 +49,51 @@ the first argument or --name=<name>. Non-interactive flags: --yes, --pm=<npm|yar
53
49
  --db=<driver>|--no-db, --features=<list>, --ai=<list>, --git|--no-git, --jwt|--no-jwt.
54
50
  ```
55
51
 
56
- | Flag | Takes value | Default | Purpose |
57
- | ------------------------ | ----------- | -------------- | ------------------------------------------------------------------- |
58
- | `<positional>` / `--name`| yes | — (required) | Project name + target directory. |
59
- | `--db` | yes | `mongodb` | Database driver key (`mongodb`, `postgres`, or `none`). |
60
- | `--no-db` | no | — | Opt out of a database — shorthand for `--db=none`. |
61
- | `--pm` | yes | auto-detected | Package manager — must be one of `npm` / `yarn` / `pnpm` / `bun`; anything else is rejected before scaffolding starts. |
62
- | `--features` | yes (CSV) | none | Comma-separated optional feature keys. |
63
- | `--ai` | yes (CSV) | none | Comma-separated AI provider keys (auto-pulls `@warlock.js/ai`). |
64
- | `--git` / `--no-git` | no | off | Initialize a Git repo (`git init` + `main` branch + initial commit).|
65
- | `--jwt` / `--no-jwt` | no | off | Generate JWT secrets via the project's `jwt` script. |
66
- | `--yes` / `-y` | no | off | Non-interactive mode. |
52
+ ## After scaffolding: the decisions summary
53
+
54
+ Because almost every choice defaults silently, `create-warlock` prints what it
55
+ decided right after scaffolding, e.g.:
67
56
 
68
- Value flags accept either `--db=postgres` or `--db postgres`. Unknown `--features` / `--ai` keys fail fast before any install.
57
+ ```
58
+ Using npm · database: none · agent-kit targets: claude
59
+ change these — package manager: --pm, database: --db/--no-db, agents: --agents,
60
+ features: --features, ai: --ai, git: --git/--no-git, jwt: --jwt/--no-jwt — or
61
+ re-run with --interactive for the full wizard.
62
+ ```
69
63
 
70
- `--help`/`-h` and `--version`/`-v` short-circuit before anything else — no prompt, filesystem write, or network call, and `--help` wins even over a positional project name. `--version` prints the installed `create-warlock` version.
64
+ ## Flags
65
+
66
+ | Flag | Takes value | Default | Purpose |
67
+ | --------------------------- | ----------- | -------------- | ------------------------------------------------------------------- |
68
+ | `<positional>` / `--name` | yes | — (required) | Project name + target directory. |
69
+ | `--stack` | yes | `api` | `api` or `web` — the one structural question; `web` seeds the `web` feature by default. |
70
+ | `--db` | yes | `mongodb` | Database driver key (`mongodb`, `postgres`, or `none`). |
71
+ | `--no-db` | no | — | Opt out of a database — shorthand for `--db=none`. |
72
+ | `--pm` | yes | inferred (`npm_config_user_agent` / system) | Package manager — must be one of `npm` / `yarn` / `pnpm` / `bun`; anything else is rejected before scaffolding starts. |
73
+ | `--features` | yes (CSV) | none, or `["web"]` when `--stack=web` and `--features` is not given | Comma-separated optional feature keys. An explicit `--features` always wins over the `--stack` default. |
74
+ | `--ai` | yes (CSV) | none | Comma-separated AI provider keys (auto-pulls `@warlock.js/ai`). |
75
+ | `--agents` | yes (CSV) | `claude` | Comma-separated `agent-kit` targets — see below. |
76
+ | `--git` / `--no-git` | no | off | Initialize a Git repo (`git init` + `main` branch + initial commit).|
77
+ | `--jwt` / `--no-jwt` | no | off | Generate JWT secrets via the project's `jwt` script. |
78
+ | `--yes` / `-y` | no | off | Fully headless mode. |
79
+ | `--interactive`, `--customize` | no | off | Restore the full long-form wizard. |
80
+
81
+ Value flags accept either `--db=postgres` or `--db postgres`. Unknown `--features` / `--ai` / `--agents` keys fail fast before any install.
82
+
83
+ `--help`/`-h` and `--version`/`-v` short-circuit before anything else — no prompt, filesystem write, or network call, and `--help` wins even over a positional project name. `--version` prints the installed `create-warlock` version. `--help` lists every flag above together with its default.
84
+
85
+ ## `--agents` and agent-kit targets
86
+
87
+ `--agents` picks which `agent-kit` targets the generated project derives
88
+ docs/skills for (written into the scaffolded `package.json`'s `agentKit.targets`
89
+ when it differs from the default). The default, `claude`, is a tiny built-in
90
+ fallback that always works — even fully offline. Any other target
91
+ (`cursor`, `codex`, `kiro`, `copilot`, `antigravity`, `opencode`, `amp`,
92
+ `goose`, …) is validated against the list published at
93
+ https://mongez.js.org/agent-kit/llms.txt, fetched once and cached locally. If
94
+ that fetch fails for an explicitly requested non-default target, the run fails
95
+ visibly and names every target it *could* determine — it never silently
96
+ guesses.
71
97
 
72
98
  ## Valid keys
73
99
 
@@ -110,11 +136,12 @@ The template ships a working auth module (login / logout / logout-all / refresh
110
136
 
111
137
  ## Setup steps the scaffolder runs
112
138
 
113
- 1. Copy template → substitute name → wire `DB_DRIVER` / `DB_PORT` into `.env` (or, for `--db=none`, remove `src/config/database.ts`).
139
+ 1. Copy template → substitute name → wire `DB_DRIVER` / `DB_PORT` into `.env` (or, for `--db=none`, remove `src/config/database.ts`) → record non-default `--agents` targets into `package.json`'s `agentKit.targets`.
114
140
  2. Install base dependencies (so the `warlock` binary exists).
115
141
  3. `warlock add <driver> <features...> <ai...> --no-install`, then one batched install. The `<driver>` is omitted for `--db=none`.
116
142
  4. `git init` (if `--git`).
117
143
  5. `jwt` script (if `--jwt`) — otherwise `warlock --warm-cache`.
144
+ 6. Print the decisions summary.
118
145
 
119
146
  Failures surface the error and halt; there is no rollback. After it finishes, `cd my-app` and run the dev server.
120
147
 
@@ -110,7 +110,7 @@ Three things that trip everyone up on a fresh app:
110
110
  field is required" message), but it is redundant on a plain field, so the
111
111
  generated code and these docs don't use it.
112
112
  - **Declare server-set foreign keys on the MODEL schema.** A controller that
113
- sets a column the client doesn't send (e.g. `authorId: request.user.id`) must
113
+ sets a column the client doesn't send (e.g. `authorId: request.locals.user.id`) must
114
114
  still have that column declared on the model's seal schema
115
115
  (`authorId: v.number()`), or `Model.create()` rejects it as an unknown key.
116
116
  The client-facing request schema and the model schema are separate, so this
@@ -354,11 +354,11 @@ createLeadController.validation = {
354
354
 
355
355
  | Route kind | Controller type | What it adds |
356
356
  | -------------------------------- | ------------------------------------- | ------------------------------------- |
357
- | Guarded (auth required) + body | `GuardedRequestHandler<Schema>` | typed `request.validated()` + `request.user` |
357
+ | Guarded (auth required) + body | `GuardedRequestHandler<Schema>` | typed `request.validated()` + `request.locals.user` |
358
358
  | Public + body | `RequestHandler<Request<Schema>>` | typed `request.validated()` |
359
359
  | Read / list (no body) | `RequestHandler` | nothing extra |
360
360
 
361
- `GuardedRequestHandler<Schema>` (defined in `app/auth/requests/guarded.request.ts`) is `RequestHandler<GuardedRequest<Schema>>` — it both types the validated payload and guarantees `request.user` is present.
361
+ `GuardedRequestHandler<Schema>` (defined in `app/auth/requests/guarded.request.ts`) is `RequestHandler<GuardedRequest<Schema>>` — it both types the validated payload and guarantees `request.locals.user` is present.
362
362
 
363
363
  ### 6.3 Accessing inputs
364
364
 
@@ -95,7 +95,7 @@ AuthN proves who you are. AuthZ decides what you can do. Conflating them is how
95
95
  const order = await ordersRepository.find({ id, userId: request.input("userId") });
96
96
 
97
97
  // ✅ identity from the authenticated session
98
- const user = request.user;
98
+ const user = request.locals.user;
99
99
  const order = await ordersRepository.find({ id, userId: user.id });
100
100
  ```
101
101
 
@@ -9,7 +9,7 @@ import { logoutAllService } from "../services/auth.service";
9
9
  * Guarded route — see the note on `logoutController`.
10
10
  */
11
11
  export const logoutAllController: GuardedRequestHandler = async ({ request, response }) => {
12
- await logoutAllService(request.user);
12
+ await logoutAllService(request.locals.user);
13
13
 
14
14
  return response.success({
15
15
  message: t("auth.loggedOutAll"),
@@ -7,11 +7,11 @@ import { logoutService } from "../services/auth.service";
7
7
  * POST /auth/logout
8
8
  *
9
9
  * Typed as `GuardedRequestHandler` because the route sits inside `guarded()`
10
- * (see `../routes.ts`). A plain `RequestHandler` types `request.user` as
11
- * `RequestUser | undefined`, which is not assignable to `logoutService`.
10
+ * (see `../routes.ts`). A plain `RequestHandler` types `request.locals.user`
11
+ * as `RequestUser | undefined`, which is not assignable to `logoutService`.
12
12
  */
13
13
  export const logoutController: GuardedRequestHandler = async ({ request, response }) => {
14
- await logoutService(request.user);
14
+ await logoutService(request.locals.user);
15
15
 
16
16
  return response.success({
17
17
  message: t("auth.loggedOut"),
@@ -11,7 +11,7 @@ import { type GuardedRequestHandler } from "app/auth/requests/guarded.request";
11
11
  */
12
12
  export const meController: GuardedRequestHandler = async ({ request, response }) => {
13
13
  return response.success({
14
- user: request.user,
14
+ user: request.locals.user,
15
15
  });
16
16
  };
17
17
 
@@ -1,8 +1,8 @@
1
- import type { Request, RequestHandler } from "@warlock.js/core";
1
+ import type { Request, RequestHandler, RequestLocals } from "@warlock.js/core";
2
2
  import type { User } from "app/users/models/user";
3
3
 
4
4
  export type GuardedRequest<RequestPayload = unknown> = Request<RequestPayload> & {
5
- user: User;
5
+ locals: RequestLocals & { user: User };
6
6
  };
7
7
 
8
8
  export type GuardedRequestHandler<RequestPayload = unknown> = RequestHandler<
@@ -8,7 +8,7 @@ export const createNewPostController: GuardedRequestHandler<CreatePostSchema> =
8
8
  }) => {
9
9
  const post = await Post.create({
10
10
  ...request.validated(),
11
- authorId: request.user.id,
11
+ authorId: request.locals.user.id,
12
12
  });
13
13
 
14
14
  return response.successCreate({
@@ -8,5 +8,6 @@ export default Migration.create(User, {
8
8
  image: string(500).nullable(),
9
9
  imageMetadata: json().nullable(),
10
10
  lastLogin: timestamp().nullable(),
11
+ emailVerifiedAt: timestamp().nullable(),
11
12
  deletedAt: timestamp().nullable(),
12
13
  });
@@ -7,13 +7,21 @@ import { UserResource } from "app/users/resources/user.resource";
7
7
  export const userSchema = v.object({
8
8
  name: v.string().required(),
9
9
  email: v.email().requiredIfEmpty("id"),
10
- image: v.string(),
10
+ // Required at the controller layer (`create-user.schema.ts`) on
11
+ // registration, but not here: a freshly-registered user's record is
12
+ // written before that upload completes, and a seeded/social-login user may
13
+ // never get an avatar at all.
14
+ image: v.string().optional(),
11
15
  password: v.string().min(6).requiredIfEmpty("id").addTransformer(useHashedPassword()),
12
16
  // Written by the social-login handler (`app/users/services/login-social.ts`).
13
17
  // A field only reachable through `save({ merge })` still has to be declared
14
18
  // here — `merge` is typed against this schema, so an undeclared key is a
15
- // compile error, not a silent write.
16
- lastLogin: v.date(),
19
+ // compile error, not a silent write. Optional: a user who has never logged
20
+ // in (freshly registered or freshly seeded) has no lastLogin yet.
21
+ lastLogin: v.date().optional(),
22
+ // Stamped by `@warlock.js/auth`'s `verifyEmail()` (the `auth.verification.field`
23
+ // default). Absent/undefined means unverified — see the `verified` scope below.
24
+ emailVerifiedAt: v.date().optional(),
17
25
  });
18
26
 
19
27
  export type UserSchema = Infer<typeof userSchema>;
@@ -54,7 +62,7 @@ export class User extends Auth<UserSchema> {
54
62
  });
55
63
 
56
64
  this.addScope("verified", (query) => {
57
- query.where("emailVerified", true);
65
+ query.whereNotNull("emailVerifiedAt");
58
66
  });
59
67
  }
60
68
  }
@@ -5,11 +5,12 @@ import type { GuardedRequestHandler } from "app/auth/requests/guarded.request";
5
5
  *
6
6
  * Despite living under `services/`, this is a route handler: it consumes the
7
7
  * request and returns a response. It is typed as a `GuardedRequestHandler` so
8
- * it can be wired straight into a route, and so `request.user` resolves to the
9
- * app's `User` model rather than core's optional `RequestUser`.
8
+ * it can be wired straight into a route, and so `request.locals.user`
9
+ * resolves to the app's `User` model rather than core's optional
10
+ * `RequestUser`.
10
11
  */
11
12
  const loginSocial: GuardedRequestHandler = async ({ request, response }) => {
12
- const user = request.user;
13
+ const user = request.locals.user;
13
14
 
14
15
  const auth = await user.generateAccessToken();
15
16
 
@@ -14,6 +14,10 @@ const databaseConfigurations: ConnectionOptions<never, PostgresPoolConfig> = {
14
14
  defaultDeleteStrategy: "permanent",
15
15
 
16
16
  clientOptions: {
17
+ // `PostgresPoolConfig` extends the connection config, so `database` is
18
+ // required here too even though it duplicates the top-level value above.
19
+ database: env("DB_NAME"),
20
+
17
21
  // Native `pg` pool options — tune these for your workload.
18
22
  // max: 10,
19
23
  // min: 0,