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.
- package/README.md +84 -0
- package/bin/cli.mjs +9 -0
- package/package.json +45 -0
- package/src/git.mjs +47 -0
- package/src/index.mjs +178 -0
- package/src/prompts.mjs +87 -0
- package/src/scaffold.mjs +66 -0
- package/src/utils.mjs +91 -0
- package/template/.agents/skills/developer/SKILL.md +93 -0
- package/template/.agents/skills/find-skills/SKILL.md +142 -0
- package/template/.agents/skills/penetration-testing/SKILL.md +94 -0
- package/template/.agents/skills/penetration-testing/references/automated-penetration-testing-framework.md +328 -0
- package/template/.agents/skills/penetration-testing/references/burp-suite-automation-script.md +135 -0
- package/template/.agents/skills/penetration-testing/scripts/security-checklist.sh +30 -0
- package/template/.agents/skills/pm/SKILL.md +202 -0
- package/template/.agents/skills/ui-ux-pro-max/SKILL.md +659 -0
- package/template/.agents/skills/ui-ux-pro-max/data/_sync_all.py +414 -0
- package/template/.agents/skills/ui-ux-pro-max/data/app-interface.csv +31 -0
- package/template/.agents/skills/ui-ux-pro-max/data/charts.csv +26 -0
- package/template/.agents/skills/ui-ux-pro-max/data/colors.csv +162 -0
- package/template/.agents/skills/ui-ux-pro-max/data/design.csv +1776 -0
- package/template/.agents/skills/ui-ux-pro-max/data/draft.csv +1779 -0
- package/template/.agents/skills/ui-ux-pro-max/data/google-fonts.csv +1924 -0
- package/template/.agents/skills/ui-ux-pro-max/data/icons.csv +106 -0
- package/template/.agents/skills/ui-ux-pro-max/data/landing.csv +35 -0
- package/template/.agents/skills/ui-ux-pro-max/data/products.csv +162 -0
- package/template/.agents/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/template/.agents/skills/ui-ux-pro-max/data/stacks/angular.csv +51 -0
- package/template/.agents/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
- package/template/.agents/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/template/.agents/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/template/.agents/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/template/.agents/skills/ui-ux-pro-max/data/stacks/laravel.csv +51 -0
- package/template/.agents/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/template/.agents/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/template/.agents/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/template/.agents/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/template/.agents/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/template/.agents/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/template/.agents/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/template/.agents/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/template/.agents/skills/ui-ux-pro-max/data/stacks/threejs.csv +54 -0
- package/template/.agents/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/template/.agents/skills/ui-ux-pro-max/data/styles.csv +85 -0
- package/template/.agents/skills/ui-ux-pro-max/data/typography.csv +74 -0
- package/template/.agents/skills/ui-ux-pro-max/data/ui-reasoning.csv +162 -0
- package/template/.agents/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/template/.agents/skills/ui-ux-pro-max/scripts/core.py +262 -0
- package/template/.agents/skills/ui-ux-pro-max/scripts/design_system.py +1148 -0
- package/template/.agents/skills/ui-ux-pro-max/scripts/search.py +114 -0
- package/template/.claude/agents/analyst.md +61 -0
- package/template/.claude/agents/developer.md +144 -0
- package/template/.claude/agents/document.md +18 -0
- package/template/.claude/agents/fixer.md +67 -0
- package/template/.claude/agents/initiator.md +58 -0
- package/template/.claude/agents/pm.md +371 -0
- package/template/.claude/agents/sysarch.md +356 -0
- package/template/.claude/agents/tester.md +83 -0
- package/template/.claude/skills/find-skills/SKILL.md +142 -0
- package/template/.claude/skills/penetration-testing/SKILL.md +94 -0
- package/template/.claude/skills/penetration-testing/references/automated-penetration-testing-framework.md +328 -0
- package/template/.claude/skills/penetration-testing/references/burp-suite-automation-script.md +135 -0
- package/template/.claude/skills/penetration-testing/scripts/security-checklist.sh +30 -0
- package/template/.claude/skills/ui-ux-pro-max/SKILL.md +659 -0
- package/template/.claude/skills/ui-ux-pro-max/data/_sync_all.py +414 -0
- package/template/.claude/skills/ui-ux-pro-max/data/app-interface.csv +31 -0
- package/template/.claude/skills/ui-ux-pro-max/data/charts.csv +26 -0
- package/template/.claude/skills/ui-ux-pro-max/data/colors.csv +162 -0
- package/template/.claude/skills/ui-ux-pro-max/data/design.csv +1776 -0
- package/template/.claude/skills/ui-ux-pro-max/data/draft.csv +1779 -0
- package/template/.claude/skills/ui-ux-pro-max/data/google-fonts.csv +1924 -0
- package/template/.claude/skills/ui-ux-pro-max/data/icons.csv +106 -0
- package/template/.claude/skills/ui-ux-pro-max/data/landing.csv +35 -0
- package/template/.claude/skills/ui-ux-pro-max/data/products.csv +162 -0
- package/template/.claude/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/template/.claude/skills/ui-ux-pro-max/data/stacks/angular.csv +51 -0
- package/template/.claude/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
- package/template/.claude/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/template/.claude/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/template/.claude/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/template/.claude/skills/ui-ux-pro-max/data/stacks/laravel.csv +51 -0
- package/template/.claude/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/template/.claude/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/template/.claude/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/template/.claude/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/template/.claude/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/template/.claude/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/template/.claude/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/template/.claude/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/template/.claude/skills/ui-ux-pro-max/data/stacks/threejs.csv +54 -0
- package/template/.claude/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/template/.claude/skills/ui-ux-pro-max/data/styles.csv +85 -0
- package/template/.claude/skills/ui-ux-pro-max/data/typography.csv +74 -0
- package/template/.claude/skills/ui-ux-pro-max/data/ui-reasoning.csv +162 -0
- package/template/.claude/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/template/.claude/skills/ui-ux-pro-max/scripts/core.py +262 -0
- package/template/.claude/skills/ui-ux-pro-max/scripts/design_system.py +1148 -0
- package/template/.claude/skills/ui-ux-pro-max/scripts/search.py +114 -0
- package/template/.github/prompts/analyst.prompt.md +58 -0
- package/template/.github/prompts/developer.prompt.md +100 -0
- package/template/.github/prompts/document.prompt.md +15 -0
- package/template/.github/prompts/fixer.prompt.md +64 -0
- package/template/.github/prompts/initiator.prompt.md +55 -0
- package/template/.github/prompts/pm.prompt.md +177 -0
- package/template/.github/prompts/sysarch.prompt.md +352 -0
- package/template/.github/prompts/tester.prompt.md +80 -0
- package/template/.opencode/commands/analyst.md +61 -0
- package/template/.opencode/commands/developer.md +174 -0
- package/template/.opencode/commands/document.md +18 -0
- package/template/.opencode/commands/fixer.md +67 -0
- package/template/.opencode/commands/initiator.md +58 -0
- package/template/.opencode/commands/pm.md +348 -0
- package/template/.opencode/commands/sysarch.md +356 -0
- package/template/.opencode/commands/tester.md +83 -0
- package/template/.opencode/skills/penetration-testing/SKILL.md +94 -0
- package/template/.opencode/skills/penetration-testing/references/automated-penetration-testing-framework.md +328 -0
- package/template/.opencode/skills/penetration-testing/references/burp-suite-automation-script.md +135 -0
- package/template/.opencode/skills/penetration-testing/scripts/security-checklist.sh +30 -0
- package/template/.opencode/skills/ui-ux-pro-max/SKILL.md +659 -0
- package/template/.opencode/skills/ui-ux-pro-max/data/_sync_all.py +414 -0
- package/template/.opencode/skills/ui-ux-pro-max/data/app-interface.csv +31 -0
- package/template/.opencode/skills/ui-ux-pro-max/data/charts.csv +26 -0
- package/template/.opencode/skills/ui-ux-pro-max/data/colors.csv +162 -0
- package/template/.opencode/skills/ui-ux-pro-max/data/design.csv +1776 -0
- package/template/.opencode/skills/ui-ux-pro-max/data/draft.csv +1779 -0
- package/template/.opencode/skills/ui-ux-pro-max/data/google-fonts.csv +1924 -0
- package/template/.opencode/skills/ui-ux-pro-max/data/icons.csv +106 -0
- package/template/.opencode/skills/ui-ux-pro-max/data/landing.csv +35 -0
- package/template/.opencode/skills/ui-ux-pro-max/data/products.csv +162 -0
- package/template/.opencode/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/template/.opencode/skills/ui-ux-pro-max/data/stacks/angular.csv +51 -0
- package/template/.opencode/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
- package/template/.opencode/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/template/.opencode/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/template/.opencode/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/template/.opencode/skills/ui-ux-pro-max/data/stacks/laravel.csv +51 -0
- package/template/.opencode/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/template/.opencode/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/template/.opencode/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/template/.opencode/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/template/.opencode/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/template/.opencode/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/template/.opencode/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/template/.opencode/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/template/.opencode/skills/ui-ux-pro-max/data/stacks/threejs.csv +54 -0
- package/template/.opencode/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/template/.opencode/skills/ui-ux-pro-max/data/styles.csv +85 -0
- package/template/.opencode/skills/ui-ux-pro-max/data/typography.csv +74 -0
- package/template/.opencode/skills/ui-ux-pro-max/data/ui-reasoning.csv +162 -0
- package/template/.opencode/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/template/.opencode/skills/ui-ux-pro-max/scripts/core.py +262 -0
- package/template/.opencode/skills/ui-ux-pro-max/scripts/design_system.py +1148 -0
- package/template/.opencode/skills/ui-ux-pro-max/scripts/search.py +114 -0
- package/template/GIT_STRUCTURE_GUIDE.md +270 -0
- package/template/PROJECT_README.example.md +81 -0
- package/template/README.md +52 -0
- package/template/_gitignore +148 -0
- package/template/agent/workflows/analyst.md +58 -0
- package/template/agent/workflows/developer.md +66 -0
- package/template/agent/workflows/document.md +15 -0
- package/template/agent/workflows/fixer.md +64 -0
- package/template/agent/workflows/initiator.md +55 -0
- package/template/agent/workflows/pm.md +177 -0
- package/template/agent/workflows/sysacrh.md +352 -0
- package/template/agent/workflows/tester.md +80 -0
- package/template/codes/.gitkeep +1 -0
- package/template/project_overview_example.md +41 -0
- package/template/skills-lock.json +11 -0
- package/template/specifications/.gitkeep +1 -0
- 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
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
|
+
}
|
package/src/prompts.mjs
ADDED
|
@@ -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
|
+
}
|
package/src/scaffold.mjs
ADDED
|
@@ -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
|
+
}
|