create-vasvibe 0.1.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 (170) hide show
  1. package/README.md +84 -0
  2. package/bin/cli.mjs +9 -0
  3. package/package.json +45 -0
  4. package/src/git.mjs +47 -0
  5. package/src/index.mjs +178 -0
  6. package/src/prompts.mjs +87 -0
  7. package/src/scaffold.mjs +66 -0
  8. package/src/utils.mjs +91 -0
  9. package/template/.agents/skills/developer/SKILL.md +93 -0
  10. package/template/.agents/skills/find-skills/SKILL.md +142 -0
  11. package/template/.agents/skills/penetration-testing/SKILL.md +94 -0
  12. package/template/.agents/skills/penetration-testing/references/automated-penetration-testing-framework.md +328 -0
  13. package/template/.agents/skills/penetration-testing/references/burp-suite-automation-script.md +135 -0
  14. package/template/.agents/skills/penetration-testing/scripts/security-checklist.sh +30 -0
  15. package/template/.agents/skills/pm/SKILL.md +202 -0
  16. package/template/.agents/skills/ui-ux-pro-max/SKILL.md +659 -0
  17. package/template/.agents/skills/ui-ux-pro-max/data/_sync_all.py +414 -0
  18. package/template/.agents/skills/ui-ux-pro-max/data/app-interface.csv +31 -0
  19. package/template/.agents/skills/ui-ux-pro-max/data/charts.csv +26 -0
  20. package/template/.agents/skills/ui-ux-pro-max/data/colors.csv +162 -0
  21. package/template/.agents/skills/ui-ux-pro-max/data/design.csv +1776 -0
  22. package/template/.agents/skills/ui-ux-pro-max/data/draft.csv +1779 -0
  23. package/template/.agents/skills/ui-ux-pro-max/data/google-fonts.csv +1924 -0
  24. package/template/.agents/skills/ui-ux-pro-max/data/icons.csv +106 -0
  25. package/template/.agents/skills/ui-ux-pro-max/data/landing.csv +35 -0
  26. package/template/.agents/skills/ui-ux-pro-max/data/products.csv +162 -0
  27. package/template/.agents/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
  28. package/template/.agents/skills/ui-ux-pro-max/data/stacks/angular.csv +51 -0
  29. package/template/.agents/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
  30. package/template/.agents/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  31. package/template/.agents/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
  32. package/template/.agents/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
  33. package/template/.agents/skills/ui-ux-pro-max/data/stacks/laravel.csv +51 -0
  34. package/template/.agents/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  35. package/template/.agents/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
  36. package/template/.agents/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
  37. package/template/.agents/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  38. package/template/.agents/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
  39. package/template/.agents/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
  40. package/template/.agents/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  41. package/template/.agents/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  42. package/template/.agents/skills/ui-ux-pro-max/data/stacks/threejs.csv +54 -0
  43. package/template/.agents/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  44. package/template/.agents/skills/ui-ux-pro-max/data/styles.csv +85 -0
  45. package/template/.agents/skills/ui-ux-pro-max/data/typography.csv +74 -0
  46. package/template/.agents/skills/ui-ux-pro-max/data/ui-reasoning.csv +162 -0
  47. package/template/.agents/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  48. package/template/.agents/skills/ui-ux-pro-max/scripts/core.py +262 -0
  49. package/template/.agents/skills/ui-ux-pro-max/scripts/design_system.py +1148 -0
  50. package/template/.agents/skills/ui-ux-pro-max/scripts/search.py +114 -0
  51. package/template/.claude/agents/analyst.md +61 -0
  52. package/template/.claude/agents/developer.md +144 -0
  53. package/template/.claude/agents/document.md +18 -0
  54. package/template/.claude/agents/fixer.md +67 -0
  55. package/template/.claude/agents/initiator.md +58 -0
  56. package/template/.claude/agents/pm.md +371 -0
  57. package/template/.claude/agents/sysarch.md +356 -0
  58. package/template/.claude/agents/tester.md +83 -0
  59. package/template/.claude/skills/find-skills/SKILL.md +142 -0
  60. package/template/.claude/skills/penetration-testing/SKILL.md +94 -0
  61. package/template/.claude/skills/penetration-testing/references/automated-penetration-testing-framework.md +328 -0
  62. package/template/.claude/skills/penetration-testing/references/burp-suite-automation-script.md +135 -0
  63. package/template/.claude/skills/penetration-testing/scripts/security-checklist.sh +30 -0
  64. package/template/.claude/skills/ui-ux-pro-max/SKILL.md +659 -0
  65. package/template/.claude/skills/ui-ux-pro-max/data/_sync_all.py +414 -0
  66. package/template/.claude/skills/ui-ux-pro-max/data/app-interface.csv +31 -0
  67. package/template/.claude/skills/ui-ux-pro-max/data/charts.csv +26 -0
  68. package/template/.claude/skills/ui-ux-pro-max/data/colors.csv +162 -0
  69. package/template/.claude/skills/ui-ux-pro-max/data/design.csv +1776 -0
  70. package/template/.claude/skills/ui-ux-pro-max/data/draft.csv +1779 -0
  71. package/template/.claude/skills/ui-ux-pro-max/data/google-fonts.csv +1924 -0
  72. package/template/.claude/skills/ui-ux-pro-max/data/icons.csv +106 -0
  73. package/template/.claude/skills/ui-ux-pro-max/data/landing.csv +35 -0
  74. package/template/.claude/skills/ui-ux-pro-max/data/products.csv +162 -0
  75. package/template/.claude/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
  76. package/template/.claude/skills/ui-ux-pro-max/data/stacks/angular.csv +51 -0
  77. package/template/.claude/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
  78. package/template/.claude/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  79. package/template/.claude/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
  80. package/template/.claude/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
  81. package/template/.claude/skills/ui-ux-pro-max/data/stacks/laravel.csv +51 -0
  82. package/template/.claude/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  83. package/template/.claude/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
  84. package/template/.claude/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
  85. package/template/.claude/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  86. package/template/.claude/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
  87. package/template/.claude/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
  88. package/template/.claude/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  89. package/template/.claude/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  90. package/template/.claude/skills/ui-ux-pro-max/data/stacks/threejs.csv +54 -0
  91. package/template/.claude/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  92. package/template/.claude/skills/ui-ux-pro-max/data/styles.csv +85 -0
  93. package/template/.claude/skills/ui-ux-pro-max/data/typography.csv +74 -0
  94. package/template/.claude/skills/ui-ux-pro-max/data/ui-reasoning.csv +162 -0
  95. package/template/.claude/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  96. package/template/.claude/skills/ui-ux-pro-max/scripts/core.py +262 -0
  97. package/template/.claude/skills/ui-ux-pro-max/scripts/design_system.py +1148 -0
  98. package/template/.claude/skills/ui-ux-pro-max/scripts/search.py +114 -0
  99. package/template/.github/prompts/analyst.prompt.md +58 -0
  100. package/template/.github/prompts/developer.prompt.md +100 -0
  101. package/template/.github/prompts/document.prompt.md +15 -0
  102. package/template/.github/prompts/fixer.prompt.md +64 -0
  103. package/template/.github/prompts/initiator.prompt.md +55 -0
  104. package/template/.github/prompts/pm.prompt.md +177 -0
  105. package/template/.github/prompts/sysarch.prompt.md +352 -0
  106. package/template/.github/prompts/tester.prompt.md +80 -0
  107. package/template/.opencode/commands/analyst.md +61 -0
  108. package/template/.opencode/commands/developer.md +174 -0
  109. package/template/.opencode/commands/document.md +18 -0
  110. package/template/.opencode/commands/fixer.md +67 -0
  111. package/template/.opencode/commands/initiator.md +58 -0
  112. package/template/.opencode/commands/pm.md +348 -0
  113. package/template/.opencode/commands/sysarch.md +356 -0
  114. package/template/.opencode/commands/tester.md +83 -0
  115. package/template/.opencode/skills/penetration-testing/SKILL.md +94 -0
  116. package/template/.opencode/skills/penetration-testing/references/automated-penetration-testing-framework.md +328 -0
  117. package/template/.opencode/skills/penetration-testing/references/burp-suite-automation-script.md +135 -0
  118. package/template/.opencode/skills/penetration-testing/scripts/security-checklist.sh +30 -0
  119. package/template/.opencode/skills/ui-ux-pro-max/SKILL.md +659 -0
  120. package/template/.opencode/skills/ui-ux-pro-max/data/_sync_all.py +414 -0
  121. package/template/.opencode/skills/ui-ux-pro-max/data/app-interface.csv +31 -0
  122. package/template/.opencode/skills/ui-ux-pro-max/data/charts.csv +26 -0
  123. package/template/.opencode/skills/ui-ux-pro-max/data/colors.csv +162 -0
  124. package/template/.opencode/skills/ui-ux-pro-max/data/design.csv +1776 -0
  125. package/template/.opencode/skills/ui-ux-pro-max/data/draft.csv +1779 -0
  126. package/template/.opencode/skills/ui-ux-pro-max/data/google-fonts.csv +1924 -0
  127. package/template/.opencode/skills/ui-ux-pro-max/data/icons.csv +106 -0
  128. package/template/.opencode/skills/ui-ux-pro-max/data/landing.csv +35 -0
  129. package/template/.opencode/skills/ui-ux-pro-max/data/products.csv +162 -0
  130. package/template/.opencode/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
  131. package/template/.opencode/skills/ui-ux-pro-max/data/stacks/angular.csv +51 -0
  132. package/template/.opencode/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
  133. package/template/.opencode/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  134. package/template/.opencode/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
  135. package/template/.opencode/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
  136. package/template/.opencode/skills/ui-ux-pro-max/data/stacks/laravel.csv +51 -0
  137. package/template/.opencode/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  138. package/template/.opencode/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
  139. package/template/.opencode/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
  140. package/template/.opencode/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  141. package/template/.opencode/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
  142. package/template/.opencode/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
  143. package/template/.opencode/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  144. package/template/.opencode/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  145. package/template/.opencode/skills/ui-ux-pro-max/data/stacks/threejs.csv +54 -0
  146. package/template/.opencode/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  147. package/template/.opencode/skills/ui-ux-pro-max/data/styles.csv +85 -0
  148. package/template/.opencode/skills/ui-ux-pro-max/data/typography.csv +74 -0
  149. package/template/.opencode/skills/ui-ux-pro-max/data/ui-reasoning.csv +162 -0
  150. package/template/.opencode/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  151. package/template/.opencode/skills/ui-ux-pro-max/scripts/core.py +262 -0
  152. package/template/.opencode/skills/ui-ux-pro-max/scripts/design_system.py +1148 -0
  153. package/template/.opencode/skills/ui-ux-pro-max/scripts/search.py +114 -0
  154. package/template/GIT_STRUCTURE_GUIDE.md +270 -0
  155. package/template/PROJECT_README.example.md +81 -0
  156. package/template/README.md +52 -0
  157. package/template/_gitignore +148 -0
  158. package/template/agent/workflows/analyst.md +58 -0
  159. package/template/agent/workflows/developer.md +66 -0
  160. package/template/agent/workflows/document.md +15 -0
  161. package/template/agent/workflows/fixer.md +64 -0
  162. package/template/agent/workflows/initiator.md +55 -0
  163. package/template/agent/workflows/pm.md +177 -0
  164. package/template/agent/workflows/sysacrh.md +352 -0
  165. package/template/agent/workflows/tester.md +80 -0
  166. package/template/codes/.gitkeep +1 -0
  167. package/template/project_overview_example.md +41 -0
  168. package/template/skills-lock.json +11 -0
  169. package/template/specifications/.gitkeep +1 -0
  170. package/template/tests/.gitkeep +1 -0
package/README.md ADDED
@@ -0,0 +1,84 @@
1
+ # create-vasvibe
2
+
3
+ Scaffold a new project with VasVibe agents, prompts, and workflows preconfigured.
4
+
5
+ ```bash
6
+ npx create-vasvibe my-new-project
7
+ ```
8
+
9
+ The command produces a project folder containing the agent toolchains your team uses, plus the standard working folders (`codes/`, `specifications/`, `tests/`) with their `.gitignore` rules already in place.
10
+
11
+ ## What you get
12
+
13
+ ```
14
+ my-new-project/
15
+ ├── .agents/ # Shared skills (developer, pm, find-skills, ...)
16
+ ├── .claude/ # Claude Code agents & skills
17
+ ├── .opencode/ # OpenCode commands & skills
18
+ ├── .github/prompts/ # GitHub Copilot prompt files
19
+ ├── agent/workflows/ # Tool-agnostic workflow markdown
20
+ ├── codes/ # Source code (gitignored by default)
21
+ ├── specifications/ # Technical specs (gitignored by default)
22
+ ├── tests/ # Tests (gitignored by default)
23
+ ├── project_overview_example.md
24
+ ├── skills-lock.json
25
+ ├── GIT_STRUCTURE_GUIDE.md
26
+ ├── PROJECT_README.example.md
27
+ ├── README.md
28
+ └── .gitignore
29
+ ```
30
+
31
+ `opencode.json` is intentionally not generated. Create one yourself per your AI provider settings (see https://opencode.ai/docs).
32
+
33
+ ## Usage
34
+
35
+ ```bash
36
+ npx create-vasvibe <project-name> [options]
37
+ ```
38
+
39
+ ### Options
40
+
41
+ | Flag | Description |
42
+ |------------------|----------------------------------------------------------|
43
+ | `-y, --yes` | Skip prompts and use defaults |
44
+ | `--no-git` | Do not initialize a git repository |
45
+ | `--no-opencode` | Exclude `.opencode/` (commands, skills) |
46
+ | `--no-claude` | Exclude `.claude/` and `.agents/` |
47
+ | `--no-github` | Exclude `.github/prompts/` |
48
+ | `--no-workflows` | Exclude `agent/workflows/` |
49
+ | `-h, --help` | Show help |
50
+ | `-v, --version` | Print version |
51
+
52
+ ### Examples
53
+
54
+ ```bash
55
+ # Interactive
56
+ npx create-vasvibe my-app
57
+
58
+ # Defaults, no questions
59
+ npx create-vasvibe my-app --yes
60
+
61
+ # Only OpenCode + workflows
62
+ npx create-vasvibe my-app --yes --no-claude --no-github
63
+ ```
64
+
65
+ ## Development (for maintainers)
66
+
67
+ This package lives at `packages/create-vasvibe/` inside the monorepo. The source of truth for template content is the repo root; `template/` is generated by a sync script.
68
+
69
+ ```bash
70
+ # Sync template/ from repo root
71
+ npm run sync-template
72
+
73
+ # Test scaffold locally
74
+ node bin/cli.mjs /tmp/test-scaffold --yes
75
+
76
+ # Inspect what npm publish would include
77
+ npm pack --dry-run
78
+ ```
79
+
80
+ `prepublishOnly` runs `sync-template` automatically, so a regular `npm publish` always ships an up-to-date template.
81
+
82
+ ## License
83
+
84
+ MIT
package/bin/cli.mjs ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ // bin/cli.mjs - thin entry point that delegates to src/index.mjs.
3
+
4
+ import { main } from '../src/index.mjs';
5
+
6
+ main(process.argv).catch((err) => {
7
+ console.error(err);
8
+ process.exit(1);
9
+ });
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "create-vasvibe",
3
+ "version": "0.1.0",
4
+ "description": "Scaffold a new project with VasVibe agents, prompts, and workflows preconfigured (Claude, OpenCode, GitHub Copilot).",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-vasvibe": "./bin/cli.mjs"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "src",
12
+ "template",
13
+ "README.md"
14
+ ],
15
+ "engines": {
16
+ "node": ">=18"
17
+ },
18
+ "scripts": {
19
+ "sync-template": "node scripts/sync-template.mjs",
20
+ "prepublishOnly": "node scripts/sync-template.mjs",
21
+ "test:scaffold": "node bin/cli.mjs /tmp/test-vasvibe-scaffold --yes"
22
+ },
23
+ "dependencies": {
24
+ "picocolors": "^1.1.1",
25
+ "prompts": "^2.4.2"
26
+ },
27
+ "keywords": [
28
+ "scaffold",
29
+ "create",
30
+ "vasvibe",
31
+ "agents",
32
+ "opencode",
33
+ "claude",
34
+ "ai",
35
+ "starter",
36
+ "template"
37
+ ],
38
+ "license": "MIT",
39
+ "author": "VasVibe Team",
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "https://github.com/anomalyco/vas_vibe.git",
43
+ "directory": "packages/create-vasvibe"
44
+ }
45
+ }
package/src/git.mjs ADDED
@@ -0,0 +1,47 @@
1
+ // src/git.mjs
2
+ // Run git commands in the scaffolded project, gracefully ignoring failures.
3
+
4
+ import { spawn } from 'node:child_process';
5
+
6
+ function run(cmd, args, opts = {}) {
7
+ return new Promise((resolve) => {
8
+ const child = spawn(cmd, args, {
9
+ stdio: ['ignore', 'pipe', 'pipe'],
10
+ ...opts,
11
+ });
12
+ let stderr = '';
13
+ child.stderr.on('data', (d) => (stderr += d.toString()));
14
+ child.on('error', () => resolve({ ok: false, code: -1, stderr }));
15
+ child.on('close', (code) => resolve({ ok: code === 0, code, stderr }));
16
+ });
17
+ }
18
+
19
+ export async function gitAvailable() {
20
+ const r = await run('git', ['--version']);
21
+ return r.ok;
22
+ }
23
+
24
+ export async function initRepo(cwd) {
25
+ if (!(await gitAvailable())) return { ok: false, reason: 'git not available' };
26
+
27
+ const init = await run('git', ['init', '-b', 'main'], { cwd });
28
+ if (!init.ok) {
29
+ // Fallback for git <2.28 without -b flag.
30
+ const initFallback = await run('git', ['init'], { cwd });
31
+ if (!initFallback.ok) return { ok: false, reason: initFallback.stderr || 'git init failed' };
32
+ }
33
+
34
+ const add = await run('git', ['add', '.'], { cwd });
35
+ if (!add.ok) return { ok: false, reason: add.stderr || 'git add failed' };
36
+
37
+ const commit = await run(
38
+ 'git',
39
+ ['commit', '-m', 'chore: initial scaffold from create-vasvibe', '--no-gpg-sign'],
40
+ { cwd },
41
+ );
42
+ if (!commit.ok) {
43
+ // Likely user.email / user.name not configured. Don't hard-fail.
44
+ return { ok: true, warning: commit.stderr || 'commit skipped' };
45
+ }
46
+ return { ok: true };
47
+ }
package/src/index.mjs ADDED
@@ -0,0 +1,178 @@
1
+ // src/index.mjs
2
+ // Main orchestrator: parse args, prompt, scaffold, git init, print next steps.
3
+
4
+ import path from 'node:path';
5
+ import { promises as fs } from 'node:fs';
6
+ import { runPrompts } from './prompts.mjs';
7
+ import { scaffold } from './scaffold.mjs';
8
+ import { initRepo } from './git.mjs';
9
+ import { log, pathExists, isDirEmpty, isValidProjectName } from './utils.mjs';
10
+ import pc from 'picocolors';
11
+
12
+ const HELP = `
13
+ ${pc.bold('create-vasvibe')} — scaffold a project with VasVibe agents preconfigured
14
+
15
+ ${pc.bold('Usage:')}
16
+ npx create-vasvibe <project-name> [options]
17
+
18
+ ${pc.bold('Options:')}
19
+ -y, --yes Skip prompts and use defaults
20
+ --no-git Do not initialize a git repository
21
+ --no-opencode Exclude .opencode/ (commands, skills)
22
+ --no-claude Exclude .claude/ and .agents/
23
+ --no-github Exclude .github/prompts/
24
+ --no-workflows Exclude agent/workflows/
25
+ -h, --help Show this help
26
+ -v, --version Print version
27
+
28
+ ${pc.bold('Examples:')}
29
+ npx create-vasvibe my-app
30
+ npx create-vasvibe my-app --yes
31
+ npx create-vasvibe my-app --yes --no-claude --no-github
32
+ `;
33
+
34
+ function parseArgs(argv) {
35
+ const args = argv.slice(2);
36
+ const flags = {
37
+ yes: false,
38
+ git: true,
39
+ opencode: true,
40
+ claude: true,
41
+ github: true,
42
+ workflows: true,
43
+ help: false,
44
+ version: false,
45
+ };
46
+ const positional = [];
47
+
48
+ for (const a of args) {
49
+ switch (a) {
50
+ case '-y':
51
+ case '--yes':
52
+ flags.yes = true;
53
+ break;
54
+ case '--no-git':
55
+ flags.git = false;
56
+ break;
57
+ case '--no-opencode':
58
+ flags.opencode = false;
59
+ break;
60
+ case '--no-claude':
61
+ flags.claude = false;
62
+ break;
63
+ case '--no-github':
64
+ flags.github = false;
65
+ break;
66
+ case '--no-workflows':
67
+ flags.workflows = false;
68
+ break;
69
+ case '-h':
70
+ case '--help':
71
+ flags.help = true;
72
+ break;
73
+ case '-v':
74
+ case '--version':
75
+ flags.version = true;
76
+ break;
77
+ default:
78
+ if (a.startsWith('-')) {
79
+ log.error(`Unknown flag: ${a}`);
80
+ console.log(HELP);
81
+ process.exit(1);
82
+ }
83
+ positional.push(a);
84
+ }
85
+ }
86
+
87
+ return { positional, flags };
88
+ }
89
+
90
+ async function readPkg() {
91
+ const url = new URL('../package.json', import.meta.url);
92
+ const raw = await fs.readFile(url, 'utf8');
93
+ return JSON.parse(raw);
94
+ }
95
+
96
+ export async function main(argv) {
97
+ const { positional, flags } = parseArgs(argv);
98
+
99
+ if (flags.help) {
100
+ console.log(HELP);
101
+ return;
102
+ }
103
+ if (flags.version) {
104
+ const pkg = await readPkg();
105
+ console.log(pkg.version);
106
+ return;
107
+ }
108
+
109
+ const argInput = positional[0];
110
+ // The arg may be a path (e.g. /tmp/foo or ./foo); use its basename as the
111
+ // logical project name, and resolve the full path as the target directory.
112
+ const argName = argInput ? path.basename(argInput) : undefined;
113
+ if (argName && !isValidProjectName(argName)) {
114
+ log.error(
115
+ `Invalid project name "${argName}". Use letters, numbers, dot, underscore, or dash, and do not start with a dot.`,
116
+ );
117
+ process.exit(1);
118
+ }
119
+
120
+ log.title('\n create-vasvibe\n');
121
+
122
+ const answers = await runPrompts({ argName, flags });
123
+ const projectName = answers.projectName;
124
+ const targetDir = argInput
125
+ ? path.resolve(process.cwd(), argInput)
126
+ : path.resolve(process.cwd(), projectName);
127
+
128
+ // Safety: target must not exist or must be empty.
129
+ if (await pathExists(targetDir)) {
130
+ if (!(await isDirEmpty(targetDir))) {
131
+ log.error(`Target directory "${projectName}" already exists and is not empty.`);
132
+ process.exit(1);
133
+ }
134
+ }
135
+
136
+ log.blank();
137
+ log.step(`Scaffolding into ${pc.cyan(targetDir)}`);
138
+
139
+ try {
140
+ await scaffold({
141
+ targetDir,
142
+ projectName,
143
+ includeOpencode: answers.includeOpencode,
144
+ includeClaude: answers.includeClaude,
145
+ includeGithub: answers.includeGithub,
146
+ includeWorkflows: answers.includeWorkflows,
147
+ });
148
+ log.success('Template files copied');
149
+ } catch (err) {
150
+ log.error(`Scaffold failed: ${err.message}`);
151
+ process.exit(1);
152
+ }
153
+
154
+ if (answers.initGit) {
155
+ const r = await initRepo(targetDir);
156
+ if (r.ok) {
157
+ log.success(r.warning ? `git initialized (${r.warning.trim().split('\n')[0]})` : 'Git repository initialized');
158
+ } else {
159
+ log.warn(`Skipped git init: ${r.reason}`);
160
+ }
161
+ }
162
+
163
+ printNextSteps({ projectName, includeOpencode: answers.includeOpencode });
164
+ }
165
+
166
+ function printNextSteps({ projectName, includeOpencode }) {
167
+ log.blank();
168
+ log.title(' Done! Next steps:\n');
169
+ console.log(` ${pc.cyan(`cd ${projectName}`)}`);
170
+ if (includeOpencode) {
171
+ console.log(` ${pc.dim('# Create opencode.json with your AI provider config')}`);
172
+ console.log(` ${pc.dim('# (see https://opencode.ai/docs for details)')}`);
173
+ }
174
+ console.log(` ${pc.dim('# Add code under codes/, specs under specifications/, tests under tests/')}`);
175
+ log.blank();
176
+ console.log(pc.dim(' Happy building.'));
177
+ log.blank();
178
+ }
@@ -0,0 +1,87 @@
1
+ // src/prompts.mjs
2
+ // Interactive prompts. Skipped when --yes is passed.
3
+
4
+ import prompts from 'prompts';
5
+ import { isValidProjectName } from './utils.mjs';
6
+
7
+ export async function runPrompts({ argName, flags }) {
8
+ if (flags.yes) {
9
+ return {
10
+ projectName: argName || 'my-vasvibe-project',
11
+ includeOpencode: flags.opencode !== false,
12
+ includeClaude: flags.claude !== false,
13
+ includeGithub: flags.github !== false,
14
+ includeWorkflows: flags.workflows !== false,
15
+ initGit: flags.git !== false,
16
+ };
17
+ }
18
+
19
+ const onCancel = () => {
20
+ console.log('\nAborted.');
21
+ process.exit(1);
22
+ };
23
+
24
+ const answers = await prompts(
25
+ [
26
+ {
27
+ type: argName ? null : 'text',
28
+ name: 'projectName',
29
+ message: 'Project name:',
30
+ initial: 'my-vasvibe-project',
31
+ validate: (v) =>
32
+ isValidProjectName(v) ||
33
+ 'Use letters, numbers, dot, underscore, or dash. Cannot start with a dot.',
34
+ },
35
+ {
36
+ type: flags.opencode === false ? null : 'toggle',
37
+ name: 'includeOpencode',
38
+ message: 'Include OpenCode commands & skills (.opencode/)?',
39
+ initial: true,
40
+ active: 'yes',
41
+ inactive: 'no',
42
+ },
43
+ {
44
+ type: flags.claude === false ? null : 'toggle',
45
+ name: 'includeClaude',
46
+ message: 'Include Claude agents & skills (.claude/, .agents/)?',
47
+ initial: true,
48
+ active: 'yes',
49
+ inactive: 'no',
50
+ },
51
+ {
52
+ type: flags.github === false ? null : 'toggle',
53
+ name: 'includeGithub',
54
+ message: 'Include GitHub Copilot prompts (.github/prompts/)?',
55
+ initial: true,
56
+ active: 'yes',
57
+ inactive: 'no',
58
+ },
59
+ {
60
+ type: flags.workflows === false ? null : 'toggle',
61
+ name: 'includeWorkflows',
62
+ message: 'Include generic agent workflows (agent/workflows/)?',
63
+ initial: true,
64
+ active: 'yes',
65
+ inactive: 'no',
66
+ },
67
+ {
68
+ type: flags.git === false ? null : 'toggle',
69
+ name: 'initGit',
70
+ message: 'Initialize a git repository?',
71
+ initial: true,
72
+ active: 'yes',
73
+ inactive: 'no',
74
+ },
75
+ ],
76
+ { onCancel },
77
+ );
78
+
79
+ return {
80
+ projectName: argName || answers.projectName,
81
+ includeOpencode: flags.opencode === false ? false : answers.includeOpencode ?? true,
82
+ includeClaude: flags.claude === false ? false : answers.includeClaude ?? true,
83
+ includeGithub: flags.github === false ? false : answers.includeGithub ?? true,
84
+ includeWorkflows: flags.workflows === false ? false : answers.includeWorkflows ?? true,
85
+ initGit: flags.git === false ? false : answers.initGit ?? true,
86
+ };
87
+ }
@@ -0,0 +1,66 @@
1
+ // src/scaffold.mjs
2
+ // Copy the bundled template into the target directory, applying selective
3
+ // includes and small file transforms (rename, placeholder replacement).
4
+
5
+ import { promises as fs } from 'node:fs';
6
+ import path from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+ import { copyDir, removePath, replaceInFile, pathExists } from './utils.mjs';
9
+
10
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
11
+ // packages/create-vasvibe/src → packages/create-vasvibe/template
12
+ const TEMPLATE_DIR = path.resolve(__dirname, '..', 'template');
13
+
14
+ const TOOLCHAIN_PATHS = {
15
+ opencode: ['.opencode'],
16
+ claude: ['.claude', '.agents'],
17
+ github: ['.github'],
18
+ workflows: ['agent'],
19
+ };
20
+
21
+ export async function scaffold({
22
+ targetDir,
23
+ projectName,
24
+ includeOpencode,
25
+ includeClaude,
26
+ includeGithub,
27
+ includeWorkflows,
28
+ }) {
29
+ if (!(await pathExists(TEMPLATE_DIR))) {
30
+ throw new Error(
31
+ `Template directory not found at ${TEMPLATE_DIR}. ` +
32
+ `If you are running from source, run "npm run sync-template" first.`,
33
+ );
34
+ }
35
+
36
+ await fs.mkdir(targetDir, { recursive: true });
37
+
38
+ // 1. Copy entire template tree.
39
+ await copyDir(TEMPLATE_DIR, targetDir);
40
+
41
+ // 2. Apply toolchain filters (delete excluded folders).
42
+ if (!includeOpencode) await removeAll(targetDir, TOOLCHAIN_PATHS.opencode);
43
+ if (!includeClaude) await removeAll(targetDir, TOOLCHAIN_PATHS.claude);
44
+ if (!includeGithub) await removeAll(targetDir, TOOLCHAIN_PATHS.github);
45
+ if (!includeWorkflows) await removeAll(targetDir, TOOLCHAIN_PATHS.workflows);
46
+
47
+ // 3. Rename _gitignore -> .gitignore (npm strips real .gitignore from packages).
48
+ const gi = path.join(targetDir, '_gitignore');
49
+ if (await pathExists(gi)) {
50
+ await fs.rename(gi, path.join(targetDir, '.gitignore'));
51
+ }
52
+
53
+ // 4. Replace placeholders in well-known files.
54
+ const year = String(new Date().getFullYear());
55
+ const replacements = { projectName, year };
56
+ for (const rel of ['README.md', 'PROJECT_README.example.md', '.gitignore']) {
57
+ const f = path.join(targetDir, rel);
58
+ if (await pathExists(f)) await replaceInFile(f, replacements);
59
+ }
60
+ }
61
+
62
+ async function removeAll(targetDir, relPaths) {
63
+ for (const rel of relPaths) {
64
+ await removePath(path.join(targetDir, rel));
65
+ }
66
+ }
package/src/utils.mjs ADDED
@@ -0,0 +1,91 @@
1
+ // src/utils.mjs
2
+ // Small utility helpers: logger, fs ops, placeholder substitution.
3
+
4
+ import { promises as fs } from 'node:fs';
5
+ import path from 'node:path';
6
+ import pc from 'picocolors';
7
+
8
+ export const log = {
9
+ info: (msg) => console.log(pc.cyan('ℹ'), msg),
10
+ success: (msg) => console.log(pc.green('✔'), msg),
11
+ warn: (msg) => console.log(pc.yellow('⚠'), msg),
12
+ error: (msg) => console.error(pc.red('✖'), msg),
13
+ step: (msg) => console.log(pc.dim('›'), msg),
14
+ blank: () => console.log(''),
15
+ raw: (msg) => console.log(msg),
16
+ title: (msg) => console.log(pc.bold(pc.magenta(msg))),
17
+ };
18
+
19
+ export async function pathExists(p) {
20
+ try {
21
+ await fs.access(p);
22
+ return true;
23
+ } catch {
24
+ return false;
25
+ }
26
+ }
27
+
28
+ export async function isDirEmpty(dir) {
29
+ try {
30
+ const entries = await fs.readdir(dir);
31
+ return entries.length === 0;
32
+ } catch {
33
+ return true;
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Recursively copy `src` directory into `dest`.
39
+ * Optional `filter(srcPath, relPath)` returns false to skip an entry.
40
+ */
41
+ export async function copyDir(src, dest, filter) {
42
+ await fs.mkdir(dest, { recursive: true });
43
+ const entries = await fs.readdir(src, { withFileTypes: true });
44
+ for (const entry of entries) {
45
+ const srcPath = path.join(src, entry.name);
46
+ const destPath = path.join(dest, entry.name);
47
+ const rel = path.relative(src, srcPath);
48
+ if (filter && filter(srcPath, rel) === false) continue;
49
+
50
+ if (entry.isSymbolicLink()) {
51
+ // Resolve symlink target and copy the resolved file/dir.
52
+ const real = await fs.realpath(srcPath);
53
+ const stat = await fs.stat(real);
54
+ if (stat.isDirectory()) {
55
+ await copyDir(real, destPath, filter);
56
+ } else {
57
+ await fs.copyFile(real, destPath);
58
+ }
59
+ } else if (entry.isDirectory()) {
60
+ await copyDir(srcPath, destPath, filter);
61
+ } else if (entry.isFile()) {
62
+ await fs.copyFile(srcPath, destPath);
63
+ }
64
+ }
65
+ }
66
+
67
+ export async function removePath(p) {
68
+ await fs.rm(p, { recursive: true, force: true });
69
+ }
70
+
71
+ /**
72
+ * Replace {{placeholder}} occurrences in a file in-place.
73
+ */
74
+ export async function replaceInFile(filePath, replacements) {
75
+ let content = await fs.readFile(filePath, 'utf8');
76
+ for (const [key, value] of Object.entries(replacements)) {
77
+ const re = new RegExp(`{{\\s*${escapeRegExp(key)}\\s*}}`, 'g');
78
+ content = content.replace(re, value);
79
+ }
80
+ await fs.writeFile(filePath, content, 'utf8');
81
+ }
82
+
83
+ function escapeRegExp(s) {
84
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
85
+ }
86
+
87
+ export function isValidProjectName(name) {
88
+ if (!name) return false;
89
+ // Allow simple folder names; npm-style restrictions where helpful.
90
+ return /^[a-zA-Z0-9._-]+$/.test(name) && !name.startsWith('.');
91
+ }