virastack 1.0.6 → 1.1.0-beta.1

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 (132) hide show
  1. package/README.md +29 -17
  2. package/package.json +5 -4
  3. package/src/commands/add.js +12 -5
  4. package/src/commands/init.js +68 -32
  5. package/src/i18n.js +36 -20
  6. package/src/index.js +15 -1
  7. package/src/utils/agents.js +85 -0
  8. package/src/utils/args.js +1 -1
  9. package/src/utils/git.js +18 -4
  10. package/src/utils/next-agents-md.js +40 -0
  11. package/src/utils/package-manager.js +5 -2
  12. package/src/utils/tools.js +1 -1
  13. package/templates/nextjs/.github/workflows/ci.yml +3 -0
  14. package/templates/nextjs/.prettierignore +9 -0
  15. package/templates/nextjs/.prettierrc +0 -1
  16. package/templates/nextjs/README.md +21 -15
  17. package/templates/nextjs/eslint.config.mjs +41 -0
  18. package/templates/nextjs/knip.config.js +2 -0
  19. package/templates/nextjs/package.json +14 -3
  20. package/templates/nextjs/pnpm-workspace.yaml +1 -0
  21. package/templates/nextjs/public/llms.txt +13 -11
  22. package/templates/nextjs/src/config/site.config.ts +1 -1
  23. package/templates/nextjs/src/features/landing/api/get-users.api.test.ts +34 -0
  24. package/templates/nextjs/src/features/landing/api/index.ts +1 -0
  25. package/templates/nextjs/src/features/landing/api/user.queries.ts +10 -0
  26. package/templates/nextjs/src/features/landing/components/CartDemo.test.tsx +22 -0
  27. package/templates/nextjs/src/features/landing/components/Features.tsx +1 -1
  28. package/templates/nextjs/src/features/landing/components/Hero.tsx +2 -3
  29. package/templates/nextjs/src/features/landing/components/Reveal.tsx +1 -1
  30. package/templates/nextjs/src/features/landing/constants/index.ts +1 -0
  31. package/templates/nextjs/src/features/landing/helpers/get-initials.test.ts +17 -0
  32. package/templates/nextjs/src/features/landing/hooks/use-users.ts +2 -5
  33. package/templates/nextjs/src/features/landing/icons/tanstack.tsx +2 -314
  34. package/templates/nextjs/src/test/render.tsx +19 -0
  35. package/templates/nextjs/src/test/server.ts +7 -0
  36. package/templates/nextjs/src/test/setup.ts +15 -0
  37. package/templates/nextjs/tsconfig.json +0 -2
  38. package/templates/nextjs/vitest.config.ts +13 -0
  39. package/templates/tanstack/.github/workflows/ci.yml +3 -0
  40. package/templates/tanstack/.prettierignore +9 -0
  41. package/templates/tanstack/.prettierrc +0 -1
  42. package/templates/tanstack/README.md +21 -15
  43. package/templates/tanstack/eslint.config.js +47 -0
  44. package/templates/tanstack/package.json +12 -3
  45. package/templates/tanstack/pnpm-workspace.yaml +1 -0
  46. package/templates/tanstack/public/llms.txt +12 -10
  47. package/templates/tanstack/src/config/site.config.ts +1 -1
  48. package/templates/tanstack/src/features/landing/api/get-users.api.test.ts +34 -0
  49. package/templates/tanstack/src/features/landing/api/index.ts +1 -0
  50. package/templates/tanstack/src/features/landing/api/user.queries.ts +10 -0
  51. package/templates/tanstack/src/features/landing/components/CartDemo.test.tsx +22 -0
  52. package/templates/tanstack/src/features/landing/components/Features.tsx +1 -1
  53. package/templates/tanstack/src/features/landing/components/Hero.tsx +2 -3
  54. package/templates/tanstack/src/features/landing/components/Reveal.tsx +1 -1
  55. package/templates/tanstack/src/features/landing/components/UsersDemo.tsx +1 -1
  56. package/templates/tanstack/src/features/landing/constants/index.ts +1 -0
  57. package/templates/tanstack/src/features/landing/helpers/get-initials.test.ts +17 -0
  58. package/templates/tanstack/src/features/landing/hooks/use-users.ts +3 -6
  59. package/templates/tanstack/src/features/landing/icons/tanstack.tsx +2 -314
  60. package/templates/tanstack/src/test/render.tsx +19 -0
  61. package/templates/tanstack/src/test/server.ts +7 -0
  62. package/templates/tanstack/src/test/setup.ts +15 -0
  63. package/templates/tanstack/vitest.config.ts +13 -0
  64. package/templates-i18n/nextjs/.github/workflows/ci.yml +3 -0
  65. package/templates-i18n/nextjs/.prettierignore +9 -0
  66. package/templates-i18n/nextjs/.prettierrc +0 -1
  67. package/templates-i18n/nextjs/README.md +21 -15
  68. package/templates-i18n/nextjs/eslint.config.mjs +41 -0
  69. package/templates-i18n/nextjs/knip.config.js +4 -2
  70. package/templates-i18n/nextjs/{src/messages → messages}/en.json +1 -1
  71. package/templates-i18n/nextjs/{src/messages → messages}/tr.json +1 -1
  72. package/templates-i18n/nextjs/package.json +14 -3
  73. package/templates-i18n/nextjs/pnpm-workspace.yaml +1 -0
  74. package/templates-i18n/nextjs/public/llms.txt +11 -10
  75. package/templates-i18n/nextjs/src/components/shared/LanguageSwitcher.tsx +2 -2
  76. package/templates-i18n/nextjs/src/config/site.config.ts +1 -1
  77. package/templates-i18n/nextjs/src/features/landing/api/get-users.api.test.ts +34 -0
  78. package/templates-i18n/nextjs/src/features/landing/api/index.ts +1 -0
  79. package/templates-i18n/nextjs/src/features/landing/api/user.queries.ts +10 -0
  80. package/templates-i18n/nextjs/src/features/landing/components/CartDemo.test.tsx +22 -0
  81. package/templates-i18n/nextjs/src/features/landing/components/Hero.tsx +1 -2
  82. package/templates-i18n/nextjs/src/features/landing/components/Reveal.tsx +1 -1
  83. package/templates-i18n/nextjs/src/features/landing/constants/index.ts +1 -0
  84. package/templates-i18n/nextjs/src/features/landing/helpers/get-initials.test.ts +17 -0
  85. package/templates-i18n/nextjs/src/features/landing/hooks/use-users.ts +2 -5
  86. package/templates-i18n/nextjs/src/features/landing/icons/tanstack.tsx +2 -314
  87. package/templates-i18n/nextjs/src/i18n/request.ts +1 -1
  88. package/templates-i18n/nextjs/src/test/render.tsx +26 -0
  89. package/templates-i18n/nextjs/src/test/server.ts +7 -0
  90. package/templates-i18n/nextjs/src/test/setup.ts +15 -0
  91. package/templates-i18n/nextjs/tsconfig.json +1 -2
  92. package/templates-i18n/nextjs/vitest.config.ts +13 -0
  93. package/templates-i18n/tanstack/.github/workflows/ci.yml +3 -0
  94. package/templates-i18n/tanstack/.prettierignore +12 -0
  95. package/templates-i18n/tanstack/.prettierrc +0 -1
  96. package/templates-i18n/tanstack/README.md +21 -15
  97. package/templates-i18n/tanstack/_gitignore +3 -0
  98. package/templates-i18n/tanstack/eslint.config.js +47 -0
  99. package/templates-i18n/tanstack/knip.config.js +2 -2
  100. package/templates-i18n/tanstack/messages/en.json +1 -1
  101. package/templates-i18n/tanstack/messages/tr.json +1 -1
  102. package/templates-i18n/tanstack/package.json +14 -4
  103. package/templates-i18n/tanstack/pnpm-workspace.yaml +1 -0
  104. package/templates-i18n/tanstack/public/llms.txt +7 -6
  105. package/templates-i18n/tanstack/src/components/shared/LanguageSwitcher.tsx +2 -2
  106. package/templates-i18n/tanstack/src/config/site.config.ts +1 -1
  107. package/templates-i18n/tanstack/src/features/landing/api/get-users.api.test.ts +34 -0
  108. package/templates-i18n/tanstack/src/features/landing/api/index.ts +1 -0
  109. package/templates-i18n/tanstack/src/features/landing/api/user.queries.ts +10 -0
  110. package/templates-i18n/tanstack/src/features/landing/components/CartDemo.test.tsx +22 -0
  111. package/templates-i18n/tanstack/src/features/landing/components/Hero.tsx +1 -2
  112. package/templates-i18n/tanstack/src/features/landing/components/Reveal.tsx +1 -1
  113. package/templates-i18n/tanstack/src/features/landing/constants/index.ts +1 -0
  114. package/templates-i18n/tanstack/src/features/landing/helpers/get-initials.test.ts +17 -0
  115. package/templates-i18n/tanstack/src/features/landing/hooks/use-users.ts +3 -6
  116. package/templates-i18n/tanstack/src/features/landing/icons/tanstack.tsx +2 -314
  117. package/templates-i18n/tanstack/src/test/render.tsx +19 -0
  118. package/templates-i18n/tanstack/src/test/server.ts +7 -0
  119. package/templates-i18n/tanstack/src/test/setup.ts +15 -0
  120. package/templates-i18n/tanstack/vitest.config.ts +13 -0
  121. package/templates/nextjs/.eslintcache +0 -1
  122. package/templates/nextjs/src/features/landing/data/index.ts +0 -1
  123. package/templates/tanstack/.eslintcache +0 -1
  124. package/templates/tanstack/src/features/landing/data/index.ts +0 -1
  125. package/templates-i18n/nextjs/.eslintcache +0 -1
  126. package/templates-i18n/nextjs/src/features/landing/data/index.ts +0 -1
  127. package/templates-i18n/tanstack/.eslintcache +0 -1
  128. package/templates-i18n/tanstack/src/features/landing/data/index.ts +0 -1
  129. /package/templates/nextjs/src/features/landing/{data/features.data.tsx → constants/features.constants.tsx} +0 -0
  130. /package/templates/tanstack/src/features/landing/{data/features.data.tsx → constants/features.constants.tsx} +0 -0
  131. /package/templates-i18n/nextjs/src/features/landing/{data/features.data.tsx → constants/features.constants.tsx} +0 -0
  132. /package/templates-i18n/tanstack/src/features/landing/{data/features.data.tsx → constants/features.constants.tsx} +0 -0
package/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
  </picture>
9
9
  </a>
10
10
 
11
- *Production-grade scaffolding CLI for modern React — integrate ecosystem tools in one command.*
11
+ *Scaffolding CLI for Next.js and TanStack Start: ViraStack tools and AI rules in one command*
12
12
 
13
13
  [![ViraStack Start](https://img.shields.io/badge/ViraStack-Start-%2300bba7)](https://virastack.com/start/)
14
14
  [![npm version](https://img.shields.io/npm/v/virastack?color=blue)](https://www.npmjs.com/package/virastack)
@@ -23,11 +23,11 @@
23
23
 
24
24
  ### [Read the full documentation →](https://virastack.com/start/)
25
25
 
26
- ## Why ViraStack Start?
26
+ ## Features
27
27
 
28
28
  - **Interactive CLI:** Interactive prompts for template selection, project naming, and ecosystem tool integration.
29
29
  - **Production-Ready Foundations:** Scaffolds Next.js or TanStack Start projects with pre-configured feature-sliced architecture.
30
- - **Built-in AI Protocols:** Integrates [**ViraStack AI**](https://github.com/virastack/ai) automatically — architecture rules, `AGENTS.md`, and design skills from [**Emil Kowalski**](https://github.com/emilkowalski/skills) and [**Jakub Krehel**](https://github.com/jakubkrehel/make-interfaces-feel-better) — giving your coding agents a strict "constitution" from day one.
30
+ - **Built-in AI Protocols:** Asks which AI coding tools you use (Claude Code, Cursor) and sets up [**ViraStack AI**](https://github.com/virastack/ai) for them — architecture rules in each tool's native format, `AGENTS.md`, and design skills from [**Emil Kowalski**](https://github.com/emilkowalski/skills) and [**Jakub Krehel**](https://github.com/jakubkrehel/skills) — giving your coding agents a strict "constitution" from day one.
31
31
  - **End-to-End Type Safety:** Strict TypeScript rules, Zod validation, and native package manager alignment (pnpm, npm, yarn, bun).
32
32
  - **Ecosystem Integration:** Add optional ViraStack tools (`@virastack/mask`, `@virastack/password`) seamlessly.
33
33
 
@@ -57,6 +57,10 @@ Which template would you like to use?
57
57
  › Next.js App Router
58
58
  TanStack Start
59
59
 
60
+ Which AI coding tools do you use?
61
+ [x] Claude Code
62
+ [x] Cursor
63
+
60
64
  Would you like multi-language (i18n) support?
61
65
  › Yes
62
66
  No
@@ -66,6 +70,19 @@ Which ViraStack tools would you like to include?
66
70
  [ ] @virastack/password
67
71
  ```
68
72
 
73
+ Select none to get plain rules in `.agents/rules/` that any agent can read through `AGENTS.md`.
74
+
75
+ ## What it sets up
76
+
77
+ 1. Copies the template and sets the project name.
78
+ 2. Installs dependencies with the package manager you ran it with.
79
+ 3. Next.js only: adds the Next.js block to `AGENTS.md`, pointing agents at the docs bundled in `node_modules/next/dist/docs/`.
80
+ 4. Runs `@virastack/ai init --agent <your choice>`. It never overwrites files you changed.
81
+ 5. Installs three design skills: `emil-design-eng` ([Emil Kowalski](https://github.com/emilkowalski/skills)), `better-ui` and `better-accessibility` ([Jakub Krehel](https://github.com/jakubkrehel/skills)). They go to `.agents/skills/` and, with Claude Code, to `.claude/skills/`. With Claude Code as the only tool, they go only to `.claude/skills/`.
82
+ 6. Creates the first git commit, so `git status` is clean.
83
+
84
+ `--skip-install` skips step 2. Offline, steps 2, 4 and 5 are skipped and the commands to run later are printed.
85
+
69
86
  ## Tools
70
87
 
71
88
  | Tool | Description |
@@ -83,9 +100,10 @@ Which ViraStack tools would you like to include?
83
100
  | `--name <name>` | Project name (or pass as positional / `.`) |
84
101
  | `--template <name>` | `nextjs` or `tanstack` |
85
102
  | `--tools <list>` | Comma-separated tools (`mask,password`) |
103
+ | `--agent <list>` | AI coding tools: `claude`, `cursor` (comma separated) or `none`. Default with `--yes`: both |
86
104
  | `--i18n` / `--no-i18n` | Enable or disable the i18n template |
87
105
  | `--yes`, `-y` | Non-interactive mode (flags + defaults) |
88
- | `--skip-install` | Scaffold files only; skip install + AI setup |
106
+ | `--skip-install` | Skip dependency install (AI rules and skills are still added) |
89
107
  | `--tr` | Turkish prompts |
90
108
  | `--telemetry-disable` | Permanently disable anonymous usage tracking |
91
109
  | `-v`, `--version` | Print CLI version |
@@ -95,6 +113,7 @@ Which ViraStack tools would you like to include?
95
113
  pnpm dlx virastack add mask
96
114
  pnpm dlx virastack add password
97
115
  pnpm dlx virastack add ai
116
+ pnpm dlx virastack add ai --agent cursor
98
117
  ```
99
118
 
100
119
  ## Included by default
@@ -106,30 +125,23 @@ Every template ships with this stack already installed:
106
125
  | **State** | TanStack Query, Zustand, nuqs |
107
126
  | **Form** | React Hook Form, Zod |
108
127
  | **Hooks** | `usehooks-ts` |
109
- | **UI** | Base UI, Framer Motion, Lucide React |
128
+ | **UI** | Base UI, Motion, Lucide React |
129
+ | **Test** | Vitest, Testing Library, MSW |
110
130
 
111
131
  [ViraStack AI](https://github.com/virastack/ai) encodes these choices as agent rules so assistants follow the same stack from day one.
112
132
 
113
133
  ## Telemetry
114
134
 
115
- To keep usage statistics, ViraStack collects strictly anonymous data (template, i18n choice, selected tools, package manager, and CLI version). Absolutely no personal data, project names, or file paths are collected.
116
-
117
- Opt out anytime: `pnpm dlx virastack --telemetry-disable`
135
+ The CLI collects anonymous usage data only: template, options, tools, package manager and CLI version. Opt out: `pnpm dlx virastack --telemetry-disable`
118
136
 
119
137
  ## Contributing
120
138
 
121
139
  Ideas and bug reports are welcome — open an [issue](https://github.com/virastack/start/issues).
122
140
 
123
- ## Explore the ViraStack Ecosystem
141
+ ## Ecosystem
124
142
 
125
- Discover all ViraStack tools, libraries, and boilerplates at [**virastack.com**](https://virastack.com).
143
+ Part of the [ViraStack](https://virastack.com) ecosystem: [Academy](https://github.com/virastack/academy) · [AI](https://github.com/virastack/ai) · [Mask](https://github.com/virastack/mask) · [Password](https://github.com/virastack/password) · [Guide](https://github.com/virastack/guide)
126
144
 
127
145
  ## License
128
146
 
129
- Licensed under the [MIT License](LICENSE).
130
-
131
- ## Maintainer
132
-
133
- A project by [**Ömer Gülçiçek**](https://omergulcicek.com)
134
-
135
- [![Follow Ömer Gülçiçek](https://img.shields.io/github/followers/omergulcicek?label=Follow&style=social)](https://github.com/omergulcicek)
147
+ [MIT](LICENSE) © [Ömer Gülçiçek](https://omergulcicek.com)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "virastack",
3
- "version": "1.0.6",
4
- "description": "Scaffold new projects and add tools from the ViraStack ecosystem with a single command.",
3
+ "version": "1.1.0-beta.1",
4
+ "description": "Scaffolding CLI for Next.js and TanStack Start: ViraStack tools and AI rules in one command",
5
5
  "author": {
6
6
  "name": "Ömer Gülçiçek (ViraStack)",
7
7
  "email": "iletisim@omergulcicek.com",
@@ -34,10 +34,11 @@
34
34
  "!src/**/*.test.js"
35
35
  ],
36
36
  "scripts": {
37
- "prepublishOnly": "find templates templates-i18n -type d \\( -name 'node_modules' -o -name '.next' -o -name 'dist' -o -name '.vinxi' -o -name '.output' -o -name '.turbo' \\) -prune -exec rm -rf '{}' +",
37
+ "prepublishOnly": "find templates templates-i18n -type d \\( -name 'node_modules' -o -name '.next' -o -name 'dist' -o -name '.vinxi' -o -name '.output' -o -name '.turbo' \\) -prune -exec rm -rf '{}' + && find templates templates-i18n -name '.eslintcache' -type f -delete",
38
38
  "start": "node ./bin/virastack.js",
39
39
  "test": "vitest run",
40
- "test:watch": "vitest"
40
+ "test:watch": "vitest",
41
+ "test:e2e": "node scripts/e2e.js"
41
42
  },
42
43
  "keywords": [
43
44
  "virastack",
@@ -8,6 +8,7 @@ import { runCommand } from "../utils/exec.js";
8
8
  import { detectPackageManager, getAddArgs } from "../utils/package-manager.js";
9
9
  import { trackEvent } from "../utils/telemetry.js";
10
10
  import { findTool, TOOLS } from "../utils/tools.js";
11
+ import { getAiInitArgs } from "../utils/agents.js";
11
12
 
12
13
  async function fileExists(filePath) {
13
14
  try {
@@ -18,7 +19,11 @@ async function fileExists(filePath) {
18
19
  }
19
20
  }
20
21
 
21
- export async function runAdd(toolId) {
22
+ /**
23
+ * @param {string | undefined} toolId
24
+ * @param {{ agents?: string[] }} [options] `agents` applies to `add ai` only.
25
+ */
26
+ export async function runAdd(toolId, options = {}) {
22
27
  console.log();
23
28
  p.intro(pc.bgMagenta(pc.black(" ViraStack ")));
24
29
 
@@ -49,9 +54,7 @@ export async function runAdd(toolId) {
49
54
  if (tool.kind === "dependency") {
50
55
  await runCommand(packageManager, getAddArgs(packageManager, [tool.package]), { cwd });
51
56
  } else {
52
- const args = ["--yes", tool.package, "init", "--force"];
53
- if (getLocale() === "tr") args.push("--tr");
54
- await runCommand("npx", args, { cwd });
57
+ await runCommand("npx", getAiInitArgs(options.agents, { tr: getLocale() === "tr" }), { cwd });
55
58
  }
56
59
  spinner.stop(t("add.done", { pkg: tool.package }));
57
60
  } catch (error) {
@@ -60,5 +63,9 @@ export async function runAdd(toolId) {
60
63
  process.exit(1);
61
64
  }
62
65
 
63
- await trackEvent("add", { tool: tool.id, packageManager });
66
+ await trackEvent("add", {
67
+ tool: tool.id,
68
+ ...(tool.id === "ai" && options.agents ? { agents: options.agents } : {}),
69
+ packageManager,
70
+ });
64
71
  }
@@ -6,7 +6,9 @@ import pc from "picocolors";
6
6
  import { t, getLocale } from "../i18n.js";
7
7
  import { copyTemplate, dirHasFiles, isDirEmpty } from "../utils/copy-template.js";
8
8
  import { runCommand } from "../utils/exec.js";
9
- import { initGitRepo } from "../utils/git.js";
9
+ import { commitInitial, initGitRepo } from "../utils/git.js";
10
+ import { AGENTS, ALL_AGENTS, SKILLS, getAiInitArgs, getSkillArgs, getSkillsCommand } from "../utils/agents.js";
11
+ import { writeNextAgentsBlock } from "../utils/next-agents-md.js";
10
12
  import { isOnline } from "../utils/is-online.js";
11
13
  import {
12
14
  detectPackageManager,
@@ -43,6 +45,7 @@ function failAndExit(message) {
43
45
  * template?: string,
44
46
  * i18n?: boolean,
45
47
  * tools?: string[] | string | true,
48
+ * agents?: string[],
46
49
  * yes?: boolean,
47
50
  * skipInstall?: boolean,
48
51
  * }} [options]
@@ -139,6 +142,25 @@ export async function runInit(options = {}) {
139
142
  template = "nextjs";
140
143
  }
141
144
 
145
+ let agents = options.agents;
146
+ if (agents == null) {
147
+ if (yes) {
148
+ agents = ALL_AGENTS;
149
+ } else {
150
+ const selected = await p.multiselect({
151
+ message: t("agents.message"),
152
+ required: false,
153
+ initialValues: ALL_AGENTS,
154
+ options: AGENTS.map((agent) => ({
155
+ value: agent.id,
156
+ label: t(`agents.${agent.id}.label`),
157
+ })),
158
+ });
159
+ if (p.isCancel(selected)) return cancelAndExit();
160
+ agents = ALL_AGENTS.filter((id) => selected.includes(id));
161
+ }
162
+ }
163
+
142
164
  let wantsI18n = options.i18n;
143
165
  if (wantsI18n == null) {
144
166
  if (yes) {
@@ -193,22 +215,15 @@ export async function runInit(options = {}) {
193
215
  await setupEnvFile(targetDir);
194
216
  scaffoldSpinner.stop(t("scaffold.copyDone"));
195
217
 
196
- const gitSpinner = p.spinner();
197
- gitSpinner.start(t("scaffold.gitInit"));
198
218
  const gitInitialized = await initGitRepo(targetDir);
199
- gitSpinner.stop(
200
- gitInitialized ? t("scaffold.gitInitDone") : t("scaffold.gitInitSkipped"),
201
- );
202
219
 
203
220
  const online = await isOnline();
204
221
  let installOk = false;
205
- let aiOk = false;
206
- let offline = false;
222
+ const offline = !online;
207
223
 
208
224
  if (skipInstall) {
209
225
  p.log.info(t("scaffold.skipInstall"));
210
- } else if (!online) {
211
- offline = true;
226
+ } else if (offline) {
212
227
  p.log.warn(t("scaffold.offline"));
213
228
  } else {
214
229
  const installSpinner = p.spinner();
@@ -221,37 +236,55 @@ export async function runInit(options = {}) {
221
236
  installSpinner.stop(t("scaffold.installFailed"));
222
237
  p.log.error(String(error?.message ?? error));
223
238
  }
239
+ }
240
+
241
+ if (template === "nextjs") {
242
+ await writeNextAgentsBlock(targetDir);
243
+ }
244
+
245
+ // ViraStack AI only writes files, so it runs even with --skip-install.
246
+ let aiOk = false;
247
+ if (!offline) {
248
+ const aiSpinner = p.spinner();
249
+ aiSpinner.start(t("scaffold.aiInit"));
250
+ try {
251
+ await runCommand("npx", getAiInitArgs(agents, { tr: getLocale() === "tr" }), { cwd: targetDir });
252
+ aiSpinner.stop(t("scaffold.aiInitDone"));
253
+ aiOk = true;
254
+ } catch {
255
+ aiSpinner.stop(t("scaffold.aiInitFailed"));
256
+ }
257
+ }
224
258
 
225
- if (installOk) {
226
- const aiSpinner = p.spinner();
227
- aiSpinner.start(t("scaffold.aiInit"));
259
+ // Each skill installs on its own: one failing must not stop the others.
260
+ // Like ViraStack AI, they only write files, so only offline skips them.
261
+ let skillsOk = false;
262
+ if (!offline) {
263
+ const skillsSpinner = p.spinner();
264
+ skillsSpinner.start(t("scaffold.skillsInit"));
265
+ let failed = 0;
266
+ for (const entry of SKILLS) {
228
267
  try {
229
- const args = ["--yes", "@virastack/ai", "init", "--force"];
230
- if (getLocale() === "tr") args.push("--tr");
231
- await runCommand("npx", args, { cwd: targetDir });
232
- aiSpinner.stop(t("scaffold.aiInitDone"));
233
- aiOk = true;
268
+ await runCommand("npx", getSkillArgs(entry, agents), { cwd: targetDir });
234
269
  } catch {
235
- aiSpinner.stop(t("scaffold.aiInitFailed"));
270
+ failed += 1;
236
271
  }
237
-
238
- if (aiOk) {
239
- try {
240
- await runCommand("npx", ["--yes", "skills@latest", "add", "emilkowalski/skills", "--skill", "emil-design-eng", "-y"], { cwd: targetDir });
241
- await runCommand("npx", ["--yes", "skills@latest", "add", "jakubkrehel/make-interfaces-feel-better", "-y"], { cwd: targetDir });
242
- } catch {
243
- p.log.warn(t("scaffold.skillsInitFailed"));
244
- }
245
- }
246
- } else {
247
- p.log.warn(t("scaffold.aiInitSkipped"));
248
272
  }
273
+ skillsOk = failed === 0;
274
+ skillsSpinner.stop(skillsOk ? t("scaffold.skillsInitDone") : t("scaffold.skillsInitFailed"));
249
275
  }
250
276
 
277
+ // Last, so the first commit contains every generated file.
278
+ const gitSpinner = p.spinner();
279
+ gitSpinner.start(t("scaffold.gitInit"));
280
+ const committed = gitInitialized && (await commitInitial(targetDir));
281
+ gitSpinner.stop(committed ? t("scaffold.gitInitDone") : t("scaffold.gitInitSkipped"));
282
+
251
283
  await trackEvent("create", {
252
284
  template,
253
285
  i18n: wantsI18n,
254
286
  tools,
287
+ agents,
255
288
  packageManager,
256
289
  installOk,
257
290
  skipInstall,
@@ -259,14 +292,16 @@ export async function runInit(options = {}) {
259
292
 
260
293
  const cdHint = useCurrentDir ? null : t("done.cd", { dir: projectName });
261
294
  const installCmd = getInstallCommand(packageManager);
262
- const aiCmd = getAiInitCommand();
295
+ const aiCmd = getAiInitCommand(agents);
296
+ const skillsCmd = getSkillsCommand();
263
297
  const devCmd = getRunDevCommand(packageManager);
264
298
 
265
299
  if (skipInstall || offline || !installOk) {
266
300
  const outroLines = [t("done.partialTitle")];
267
301
  if (cdHint) outroLines.push(cdHint);
268
302
  outroLines.push(t("done.manualInstall", { cmd: installCmd }));
269
- outroLines.push(t("done.manualAi", { cmd: aiCmd }));
303
+ if (!aiOk) outroLines.push(t("done.manualAi", { cmd: aiCmd }));
304
+ if (!skillsOk) outroLines.push(t("done.manualSkills", { cmd: skillsCmd }));
270
305
  outroLines.push(t("done.dev", { cmd: devCmd }));
271
306
  if (!installOk && !offline && !skipInstall) {
272
307
  p.log.error(t("done.installFailedNote"));
@@ -281,6 +316,7 @@ export async function runInit(options = {}) {
281
316
  const outroLines = [t("done.title")];
282
317
  if (cdHint) outroLines.push(cdHint);
283
318
  if (!aiOk) outroLines.push(t("done.manualAi", { cmd: aiCmd }));
319
+ if (!skillsOk) outroLines.push(t("done.manualSkills", { cmd: skillsCmd }));
284
320
  outroLines.push(t("done.dev", { cmd: devCmd }));
285
321
  p.outro(outroLines.join("\n"));
286
322
  }
package/src/i18n.js CHANGED
@@ -7,7 +7,7 @@ const dictionaries = {
7
7
  en: {
8
8
  cancel: "Operation cancelled.",
9
9
  "telemetry.notice":
10
- "ViraStack collects anonymous usage data (template, i18n choice, selected tools) to improve the CLI. No personal data is ever collected. Disable anytime with --telemetry-disable.",
10
+ "ViraStack collects anonymous usage data (template, i18n choice, selected tools, AI coding tools) to improve the CLI. No personal data is ever collected. Disable anytime with --telemetry-disable.",
11
11
 
12
12
  "projectName.message": "What is your project named?",
13
13
  "projectName.placeholder": "virastack-app",
@@ -30,6 +30,12 @@ const dictionaries = {
30
30
  "template.errorInvalid":
31
31
  'Unknown template "{template}". Available templates: nextjs, tanstack.',
32
32
 
33
+ "agents.message": "Which AI coding tools do you use?",
34
+ "agents.claude.label": "Claude Code",
35
+ "agents.cursor.label": "Cursor",
36
+ "agent.errorInvalid":
37
+ 'Invalid --agent value "{value}". Use claude, cursor (comma separated) or none.',
38
+
33
39
  "i18n.message": "Would you like multi-language (i18n) support?",
34
40
  "i18n.active": "Yes",
35
41
  "i18n.inactive": "No",
@@ -46,9 +52,9 @@ const dictionaries = {
46
52
 
47
53
  "scaffold.copying": "Creating project files...",
48
54
  "scaffold.copyDone": "Project files created.",
49
- "scaffold.gitInit": "Initializing git repository...",
50
- "scaffold.gitInitDone": "Git repository initialized.",
51
- "scaffold.gitInitSkipped": "Skipped git initialization.",
55
+ "scaffold.gitInit": "Creating the initial git commit...",
56
+ "scaffold.gitInitDone": "Initial git commit created.",
57
+ "scaffold.gitInitSkipped": "Skipped the initial git commit.",
52
58
  "scaffold.installing": "Installing dependencies with {pm}...",
53
59
  "scaffold.installDone": "Dependencies installed.",
54
60
  "scaffold.installFailed":
@@ -61,10 +67,9 @@ const dictionaries = {
61
67
  "scaffold.aiInitDone": "🤖 ViraStack AI agent rules successfully integrated.",
62
68
  "scaffold.aiInitFailed":
63
69
  "Failed to configure ViraStack AI rules.",
64
- "scaffold.aiInitSkipped":
65
- "Skipping AI setup because dependency install failed.",
66
- "scaffold.skillsInitFailed":
67
- "Design skills could not be installed. Run manually: npx skills@latest add emilkowalski/skills && npx skills@latest add jakubkrehel/make-interfaces-feel-better",
70
+ "scaffold.skillsInit": "Installing design skills...",
71
+ "scaffold.skillsInitDone": "Design skills installed.",
72
+ "scaffold.skillsInitFailed": "Some design skills could not be installed.",
68
73
 
69
74
  "done.title": "You're all set!",
70
75
  "done.partialTitle": "Project files are ready — finish setup manually:",
@@ -73,6 +78,7 @@ const dictionaries = {
73
78
  "done.cd": " cd {dir}",
74
79
  "done.manualInstall": " {cmd}",
75
80
  "done.manualAi": " {cmd}",
81
+ "done.manualSkills": " {cmd}",
76
82
  "done.dev": " {cmd}",
77
83
 
78
84
  "add.toolMissing": "Please specify a tool to add. Available tools: {list}.",
@@ -85,6 +91,7 @@ const dictionaries = {
85
91
  help: `Usage:
86
92
  pnpm dlx virastack [init] [name] Scaffold a new project
87
93
  pnpm dlx virastack add <tool> Add a ViraStack tool to an existing project
94
+ pnpm dlx virastack add ai --agent <list> Add AI rules for the given tools
88
95
 
89
96
  Tools:
90
97
  mask, password, ai
@@ -93,9 +100,10 @@ Options:
93
100
  --name <name> Project name (or pass as positional arg / ".")
94
101
  --template <name> Template: nextjs | tanstack
95
102
  --tools <list> Comma-separated tools (e.g. mask,password)
103
+ --agent <list> AI coding tools: claude, cursor (comma separated) or none
96
104
  --i18n / --no-i18n Enable or disable the i18n template
97
105
  --yes, -y Non-interactive mode (use flags/defaults)
98
- --skip-install Scaffold files only; skip install + AI setup
106
+ --skip-install Skip dependency install (AI rules and skills are still added)
99
107
  --tr Ask questions in Turkish
100
108
  --telemetry-disable Disable anonymous usage tracking
101
109
  -v, --version Print the version
@@ -104,13 +112,13 @@ Options:
104
112
  Examples:
105
113
  pnpm dlx virastack
106
114
  pnpm dlx virastack my-app --yes
107
- pnpm dlx virastack init my-app --template nextjs --tools mask --yes`,
115
+ pnpm dlx virastack init my-app --template nextjs --tools mask --agent claude --yes`,
108
116
  },
109
117
 
110
118
  tr: {
111
119
  cancel: "İşlem iptal edildi.",
112
120
  "telemetry.notice":
113
- "ViraStack, CLI'ı geliştirmek için anonim kullanım verisi toplar (şablon, i18n tercihi, seçilen araçlar). Hiçbir kişisel veri toplanmaz. --telemetry-disable ile her zaman kapatabilirsiniz.",
121
+ "ViraStack, CLI'ı geliştirmek için anonim kullanım verisi toplar (şablon, i18n tercihi, seçilen araçlar, AI kodlama araçları). Hiçbir kişisel veri toplanmaz. --telemetry-disable ile her zaman kapatabilirsiniz.",
114
122
 
115
123
  "projectName.message": "Projenizin adı ne olsun?",
116
124
  "projectName.placeholder": "virastack-app",
@@ -133,6 +141,12 @@ Examples:
133
141
  "template.errorInvalid":
134
142
  'Bilinmeyen şablon "{template}". Kullanılabilir şablonlar: nextjs, tanstack.',
135
143
 
144
+ "agents.message": "Hangi AI kodlama araçlarını kullanıyorsunuz?",
145
+ "agents.claude.label": "Claude Code",
146
+ "agents.cursor.label": "Cursor",
147
+ "agent.errorInvalid":
148
+ 'Geçersiz --agent değeri "{value}". claude, cursor (virgülle) veya none kullanın.',
149
+
136
150
  "i18n.message": "Çoklu dil (i18n) desteği ister misiniz?",
137
151
  "i18n.active": "Evet",
138
152
  "i18n.inactive": "Hayır",
@@ -149,9 +163,9 @@ Examples:
149
163
 
150
164
  "scaffold.copying": "Proje dosyaları oluşturuluyor...",
151
165
  "scaffold.copyDone": "Proje dosyaları oluşturuldu.",
152
- "scaffold.gitInit": "Git deposu başlatılıyor...",
153
- "scaffold.gitInitDone": "Git deposu başlatıldı.",
154
- "scaffold.gitInitSkipped": "Git başlatma adımı atlandı.",
166
+ "scaffold.gitInit": "İlk git commit'i oluşturuluyor...",
167
+ "scaffold.gitInitDone": "İlk git commit'i oluşturuldu.",
168
+ "scaffold.gitInitSkipped": "İlk git commit'i atlandı.",
155
169
  "scaffold.installing": "Bağımlılıklar {pm} ile kuruluyor...",
156
170
  "scaffold.installDone": "Bağımlılıklar kuruldu.",
157
171
  "scaffold.installFailed":
@@ -164,10 +178,9 @@ Examples:
164
178
  "scaffold.aiInitDone": "🤖 ViraStack AI ajan kuralları projeye başarıyla entegre edildi.",
165
179
  "scaffold.aiInitFailed":
166
180
  "ViraStack AI kuralları yapılandırılamadı.",
167
- "scaffold.aiInitSkipped":
168
- "Bağımlılık kurulumu başarısız olduğu için AI yapılandırması atlandı.",
169
- "scaffold.skillsInitFailed":
170
- "Tasarım skill'leri kurulamadı. Manuel çalıştırın: npx skills@latest add emilkowalski/skills && npx skills@latest add jakubkrehel/make-interfaces-feel-better",
181
+ "scaffold.skillsInit": "Tasarım skill'leri kuruluyor...",
182
+ "scaffold.skillsInitDone": "Tasarım skill'leri kuruldu.",
183
+ "scaffold.skillsInitFailed": "Bazı tasarım skill'leri kurulamadı.",
171
184
 
172
185
  "done.title": "Her şey hazır!",
173
186
  "done.partialTitle": "Proje dosyaları hazır — kurulumu manuel tamamlayın:",
@@ -176,6 +189,7 @@ Examples:
176
189
  "done.cd": " cd {dir}",
177
190
  "done.manualInstall": " {cmd}",
178
191
  "done.manualAi": " {cmd}",
192
+ "done.manualSkills": " {cmd}",
179
193
  "done.dev": " {cmd}",
180
194
 
181
195
  "add.toolMissing":
@@ -189,6 +203,7 @@ Examples:
189
203
  help: `Kullanım:
190
204
  pnpm dlx virastack [init] [ad] Yeni bir proje oluştur
191
205
  pnpm dlx virastack add <araç> Mevcut projeye bir ViraStack aracı ekle
206
+ pnpm dlx virastack add ai --agent <liste> Seçilen araçlar için AI kurallarını ekle
192
207
 
193
208
  Araçlar:
194
209
  mask, password, ai
@@ -197,9 +212,10 @@ Seçenekler:
197
212
  --name <ad> Proje adı (veya positional arg / ".")
198
213
  --template <ad> Şablon: nextjs | tanstack
199
214
  --tools <liste> Virgülle ayrılmış araçlar (örn. mask,password)
215
+ --agent <liste> AI kodlama araçları: claude, cursor (virgülle) veya none
200
216
  --i18n / --no-i18n i18n şablonunu aç / kapat
201
217
  --yes, -y İnteraktif olmayan mod (flag/varsayılanlar)
202
- --skip-install Sadece dosyaları oluştur; kurulum + AI atla
218
+ --skip-install Bağımlılık kurulumunu atla (AI kuralları ve skill'ler yine eklenir)
203
219
  --tr Soruları Türkçe sor
204
220
  --telemetry-disable Anonim kullanım verisi toplamayı kapat
205
221
  -v, --version Sürümü göster
@@ -208,7 +224,7 @@ Seçenekler:
208
224
  Örnekler:
209
225
  pnpm dlx virastack
210
226
  pnpm dlx virastack my-app --yes
211
- pnpm dlx virastack init my-app --template nextjs --tools mask --yes`,
227
+ pnpm dlx virastack init my-app --template nextjs --tools mask --agent claude --yes`,
212
228
  },
213
229
  };
214
230
 
package/src/index.js CHANGED
@@ -5,6 +5,7 @@ import { setLocale, t } from "./i18n.js";
5
5
  import { runInit } from "./commands/init.js";
6
6
  import { runAdd } from "./commands/add.js";
7
7
  import { parseToolsFlag } from "./utils/tools.js";
8
+ import { parseAgentFlag } from "./utils/agents.js";
8
9
 
9
10
  function resolveI18nFlag(flags) {
10
11
  if (hasFlag(flags, "no-i18n")) return false;
@@ -12,6 +13,18 @@ function resolveI18nFlag(flags) {
12
13
  return undefined;
13
14
  }
14
15
 
16
+ /** `--agent` as a list of ids, undefined when absent. Exits on an invalid value. */
17
+ function resolveAgentFlag(flags) {
18
+ const value = getFlag(flags, "agent");
19
+ if (value === undefined) return undefined;
20
+ const agents = parseAgentFlag(value);
21
+ if (agents === null) {
22
+ console.error(t("agent.errorInvalid", { value: value === true ? "" : value }));
23
+ process.exit(1);
24
+ }
25
+ return agents;
26
+ }
27
+
15
28
  function buildInitOptions(flags, projectName) {
16
29
  const nameFlag = getFlag(flags, "name");
17
30
  const template = getFlag(flags, "template");
@@ -22,6 +35,7 @@ function buildInitOptions(flags, projectName) {
22
35
  template: typeof template === "string" ? template : undefined,
23
36
  i18n: resolveI18nFlag(flags),
24
37
  tools: tools === undefined ? undefined : parseToolsFlag(tools),
38
+ agents: resolveAgentFlag(flags),
25
39
  yes: hasFlag(flags, "yes", "y"),
26
40
  skipInstall: hasFlag(flags, "skip-install"),
27
41
  };
@@ -55,7 +69,7 @@ export async function main() {
55
69
  await runInit(buildInitOptions(flags, rest[0]));
56
70
  break;
57
71
  case "add":
58
- await runAdd(rest[0]);
72
+ await runAdd(rest[0], { agents: resolveAgentFlag(flags) });
59
73
  break;
60
74
  default:
61
75
  // `pnpm dlx virastack my-app --yes` — first token is the project name.
@@ -0,0 +1,85 @@
1
+ import { getCliVersion } from "./package-info.js";
2
+
3
+ /**
4
+ * AI coding tools the CLI can set up. `skillsAgent` is the matching
5
+ * `skills` CLI agent id (https://github.com/vercel-labs/skills).
6
+ */
7
+ export const AGENTS = [
8
+ { id: "claude", skillsAgent: "claude-code" },
9
+ { id: "cursor", skillsAgent: "cursor" },
10
+ ];
11
+
12
+ export const ALL_AGENTS = AGENTS.map((agent) => agent.id);
13
+
14
+ /**
15
+ * Parses `--agent claude,cursor` / `--agent none`.
16
+ * Returns the selected ids in canonical order, or null when the value is
17
+ * missing or names an unknown tool.
18
+ */
19
+ export function parseAgentFlag(value) {
20
+ if (typeof value !== "string" || value.trim() === "") return null;
21
+ const parts = value.split(",").map((part) => part.trim()).filter(Boolean);
22
+ if (parts.length === 1 && parts[0] === "none") return [];
23
+ if (parts.length === 0 || parts.some((part) => !ALL_AGENTS.includes(part))) return null;
24
+ return ALL_AGENTS.filter((id) => parts.includes(id));
25
+ }
26
+
27
+ /** `--agent` value for @virastack/ai: "claude,cursor", "cursor" or "none". */
28
+ export function formatAgents(agents) {
29
+ return agents.length === 0 ? "none" : agents.join(",");
30
+ }
31
+
32
+ /**
33
+ * npm spec for @virastack/ai. A prerelease of this CLI pairs with the ai
34
+ * prerelease channel (`next`), a stable release with ai v2.
35
+ * VIRASTACK_AI_SPEC overrides it (e.g. `file:` + a local tarball in end-to-end tests).
36
+ */
37
+ export function getAiPackageSpec(cliVersion = getCliVersion()) {
38
+ if (process.env.VIRASTACK_AI_SPEC) return process.env.VIRASTACK_AI_SPEC;
39
+ return `@virastack/ai@${cliVersion.includes("-") ? "next" : "2"}`;
40
+ }
41
+
42
+ /**
43
+ * Arguments for `npx`. Never `--force`, so files the user changed are kept.
44
+ * Without `agents`, ViraStack AI reuses the saved choice (or installs both).
45
+ */
46
+ export function getAiInitArgs(agents, { tr = false } = {}) {
47
+ const args = ["--yes", getAiPackageSpec(), "init"];
48
+ if (agents) args.push("--agent", formatAgents(agents));
49
+ args.push("--yes");
50
+ if (tr) args.push("--tr");
51
+ return args;
52
+ }
53
+
54
+ /** Design skills installed in every project, one `skills add` call each. */
55
+ export const SKILLS = [
56
+ { source: "emilkowalski/skills", skill: "emil-design-eng" },
57
+ { source: "jakubkrehel/skills", skill: "better-ui" },
58
+ { source: "jakubkrehel/skills", skill: "better-accessibility" },
59
+ ];
60
+
61
+ /**
62
+ * Arguments for `npx skills@latest add`. Skills land in `.agents/skills/`
63
+ * (the `universal` target, read by Cursor and other agents) and are also
64
+ * copied for each selected tool that needs its own folder. With Claude Code
65
+ * as the only tool, `.agents/skills/` would go unused, so it is skipped.
66
+ * `--copy` writes real files instead of symlinks so the first commit works
67
+ * the same on Windows.
68
+ */
69
+ export function getSkillArgs({ source, skill }, agents) {
70
+ const tools = agents.map((id) => AGENTS.find((agent) => agent.id === id).skillsAgent);
71
+ const claudeOnly = agents.length === 1 && agents[0] === "claude";
72
+ const targets = claudeOnly ? tools : ["universal", ...tools];
73
+ const args = ["--yes", "skills@latest", "add", source];
74
+ if (skill) args.push("--skill", skill);
75
+ for (const target of targets) args.push("-a", target);
76
+ args.push("--copy", "-y");
77
+ return args;
78
+ }
79
+
80
+ /** Manual command for the skills (interactive), shown when they were not installed. */
81
+ export function getSkillsCommand() {
82
+ return SKILLS.map(({ source, skill }) =>
83
+ ["npx skills@latest add", source, ...(skill ? ["--skill", skill] : [])].join(" "),
84
+ ).join(" && ");
85
+ }
package/src/utils/args.js CHANGED
@@ -5,7 +5,7 @@
5
5
  * Value-taking long flags consume the next token when it is not itself a flag.
6
6
  */
7
7
 
8
- const VALUE_FLAGS = new Set(["name", "template", "tools"]);
8
+ const VALUE_FLAGS = new Set(["name", "template", "tools", "agent"]);
9
9
 
10
10
  /**
11
11
  * @param {string[]} argv