create-baton 1.0.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 +58 -0
- package/bin/create-baton.js +9 -0
- package/package.json +36 -0
- package/src/constants.js +30 -0
- package/src/index.js +35 -0
- package/src/prompts.js +54 -0
- package/src/scaffold.js +193 -0
- package/templates/BATON_v3.1.md +849 -0
- package/templates/ide/CLAUDE.md.template +105 -0
- package/templates/ide/cursorrules.template +64 -0
- package/templates/skills/core/anti-overengineering/SKILL.md +180 -0
- package/templates/skills/core/cost-awareness/SKILL.md +207 -0
- package/templates/skills/core/launch-prep/SKILL.md +232 -0
- package/templates/skills/core/milestones/SKILL.md +167 -0
- package/templates/skills/core/production-readiness/SKILL.md +307 -0
- package/templates/skills/core/security/SKILL.md +309 -0
- package/templates/skills/core/testing/SKILL.md +307 -0
- package/templates/skills/core/ui-ux/SKILL.md +155 -0
- package/templates/skills/patterns/api-integration/SKILL.md +143 -0
- package/templates/skills/stacks/nextjs/SKILL.md +230 -0
- package/templates/skills/stacks/supabase/SKILL.md +402 -0
package/README.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# create-baton
|
|
2
|
+
|
|
3
|
+
Set up the [Baton](https://github.com/darwesh88/baton) AI orchestration protocol in any project with one command.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm create baton
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or with npx:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx create-baton
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## What It Does
|
|
18
|
+
|
|
19
|
+
Baton turns any AI coding assistant into a self-managing project partner. This CLI sets up:
|
|
20
|
+
|
|
21
|
+
- **BATON_v3.1.md** — The core protocol
|
|
22
|
+
- **skills/** — Proven patterns library (security, testing, stack guides)
|
|
23
|
+
- **.ai-rules/** — Stub files for AI to fill during discovery
|
|
24
|
+
- **handoff/** — Session handoff directory
|
|
25
|
+
- **IDE config** — CLAUDE.md, .cursorrules, or .windsurfrules based on your tool
|
|
26
|
+
|
|
27
|
+
## How It Works
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
$ npx create-baton
|
|
31
|
+
|
|
32
|
+
Baton — AI Orchestration Protocol v3.1
|
|
33
|
+
|
|
34
|
+
? What AI coding tool are you using? → Claude Code
|
|
35
|
+
? What's your primary stack? → Next.js + Supabase
|
|
36
|
+
? Project name? → my-app
|
|
37
|
+
|
|
38
|
+
Setting up Baton...
|
|
39
|
+
|
|
40
|
+
✓ Copied BATON_v3.1.md
|
|
41
|
+
✓ Copied skills/ (8 core + 2 stack + 1 pattern)
|
|
42
|
+
✓ Created .ai-rules/ (4 stub files)
|
|
43
|
+
✓ Created handoff/
|
|
44
|
+
✓ Created CLAUDE.md
|
|
45
|
+
✓ Created PROGRESS.md, BACKLOG.md, FEATURES.md
|
|
46
|
+
|
|
47
|
+
Done! Next steps:
|
|
48
|
+
1. Open this folder in your AI coding tool
|
|
49
|
+
2. Tell the AI: "Read BATON_v3.1.md and begin"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Requirements
|
|
53
|
+
|
|
54
|
+
- Node.js 18+
|
|
55
|
+
|
|
56
|
+
## License
|
|
57
|
+
|
|
58
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-baton",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Set up Baton AI orchestration protocol in any project",
|
|
5
|
+
"bin": {
|
|
6
|
+
"create-baton": "./bin/create-baton.js"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"prep": "node scripts/prep-templates.js",
|
|
10
|
+
"prepublishOnly": "node scripts/prep-templates.js"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"ai",
|
|
14
|
+
"orchestration",
|
|
15
|
+
"protocol",
|
|
16
|
+
"claude",
|
|
17
|
+
"cursor",
|
|
18
|
+
"windsurf",
|
|
19
|
+
"baton",
|
|
20
|
+
"scaffolding"
|
|
21
|
+
],
|
|
22
|
+
"author": "darwesh88",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/darwesh88/baton.git",
|
|
27
|
+
"directory": "cli"
|
|
28
|
+
},
|
|
29
|
+
"homepage": "https://github.com/darwesh88/baton",
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=18"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"prompts": "^2.4.2"
|
|
35
|
+
}
|
|
36
|
+
}
|
package/src/constants.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// IDE tool → config file mapping
|
|
2
|
+
const IDE_MAP = {
|
|
3
|
+
'Claude Code': { file: 'CLAUDE.md', template: 'CLAUDE.md.template' },
|
|
4
|
+
'Cursor': { file: '.cursorrules', template: 'cursorrules.template' },
|
|
5
|
+
'Windsurf': { file: '.windsurfrules', template: 'cursorrules.template' },
|
|
6
|
+
'Codex': { file: 'AGENTS.md', template: null }, // AGENTS.md generated directly
|
|
7
|
+
'Kiro': { file: 'CLAUDE.md', template: 'CLAUDE.md.template' },
|
|
8
|
+
'Warp': { file: 'CLAUDE.md', template: 'CLAUDE.md.template' },
|
|
9
|
+
'Other': { file: 'CLAUDE.md', template: 'CLAUDE.md.template' },
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// Stack → which skill directories to copy into stacks/
|
|
13
|
+
const STACK_MAP = {
|
|
14
|
+
'Next.js + Supabase': ['nextjs', 'supabase'],
|
|
15
|
+
'Next.js + other': ['nextjs'],
|
|
16
|
+
'React + Node': [],
|
|
17
|
+
'Python': [],
|
|
18
|
+
'Other': [],
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// Stack → display name for templates
|
|
22
|
+
const STACK_LABEL = {
|
|
23
|
+
'Next.js + Supabase': 'Next.js, Supabase',
|
|
24
|
+
'Next.js + other': 'Next.js',
|
|
25
|
+
'React + Node': 'React, Node.js',
|
|
26
|
+
'Python': 'Python',
|
|
27
|
+
'Other': '',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
module.exports = { IDE_MAP, STACK_MAP, STACK_LABEL };
|
package/src/index.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const { askQuestions } = require('./prompts');
|
|
2
|
+
const { scaffold } = require('./scaffold');
|
|
3
|
+
|
|
4
|
+
async function main() {
|
|
5
|
+
// Banner
|
|
6
|
+
console.log('');
|
|
7
|
+
console.log(' Baton — AI Orchestration Protocol v3.1');
|
|
8
|
+
console.log('');
|
|
9
|
+
|
|
10
|
+
// Prompts
|
|
11
|
+
const answers = await askQuestions();
|
|
12
|
+
console.log('');
|
|
13
|
+
console.log(' Setting up Baton...');
|
|
14
|
+
console.log('');
|
|
15
|
+
|
|
16
|
+
// Scaffold
|
|
17
|
+
const dest = process.cwd();
|
|
18
|
+
const results = scaffold(dest, answers);
|
|
19
|
+
|
|
20
|
+
// Success
|
|
21
|
+
for (const r of results) {
|
|
22
|
+
console.log(` \u2713 ${r}`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
console.log('');
|
|
26
|
+
console.log(' Done! Next steps:');
|
|
27
|
+
console.log(' 1. Open this folder in your AI coding tool');
|
|
28
|
+
console.log(' 2. Tell the AI: "Read BATON_v3.1.md and begin"');
|
|
29
|
+
console.log('');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
main().catch((err) => {
|
|
33
|
+
console.error('Error:', err.message);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
});
|
package/src/prompts.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const prompts = require('prompts');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
async function askQuestions() {
|
|
5
|
+
// Exit gracefully on Ctrl+C
|
|
6
|
+
const onCancel = () => {
|
|
7
|
+
console.log('\nAborted.');
|
|
8
|
+
process.exit(0);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const answers = await prompts([
|
|
12
|
+
{
|
|
13
|
+
type: 'select',
|
|
14
|
+
name: 'tool',
|
|
15
|
+
message: 'What AI coding tool are you using?',
|
|
16
|
+
choices: [
|
|
17
|
+
{ title: 'Claude Code', value: 'Claude Code' },
|
|
18
|
+
{ title: 'Cursor', value: 'Cursor' },
|
|
19
|
+
{ title: 'Windsurf', value: 'Windsurf' },
|
|
20
|
+
{ title: 'Codex (OpenAI)', value: 'Codex' },
|
|
21
|
+
{ title: 'Kiro', value: 'Kiro' },
|
|
22
|
+
{ title: 'Warp', value: 'Warp' },
|
|
23
|
+
{ title: 'Other', value: 'Other' },
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
type: 'select',
|
|
28
|
+
name: 'stack',
|
|
29
|
+
message: "What's your primary stack?",
|
|
30
|
+
choices: [
|
|
31
|
+
{ title: 'Next.js + Supabase', value: 'Next.js + Supabase' },
|
|
32
|
+
{ title: 'Next.js + other', value: 'Next.js + other' },
|
|
33
|
+
{ title: 'React + Node', value: 'React + Node' },
|
|
34
|
+
{ title: 'Python', value: 'Python' },
|
|
35
|
+
{ title: 'Other', value: 'Other' },
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
type: 'text',
|
|
40
|
+
name: 'projectName',
|
|
41
|
+
message: 'Project name?',
|
|
42
|
+
initial: path.basename(process.cwd()),
|
|
43
|
+
},
|
|
44
|
+
], { onCancel });
|
|
45
|
+
|
|
46
|
+
// If prompts were cancelled (empty object), exit
|
|
47
|
+
if (!answers.tool && answers.tool !== 0) {
|
|
48
|
+
process.exit(0);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return answers;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
module.exports = { askQuestions };
|
package/src/scaffold.js
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { IDE_MAP, STACK_MAP, STACK_LABEL } = require('./constants');
|
|
4
|
+
|
|
5
|
+
const TEMPLATES_DIR = path.join(__dirname, '..', 'templates');
|
|
6
|
+
|
|
7
|
+
function scaffold(dest, { tool, stack, projectName }) {
|
|
8
|
+
const results = [];
|
|
9
|
+
|
|
10
|
+
// 1. Check for existing BATON_v3.1.md
|
|
11
|
+
const batonDest = path.join(dest, 'BATON_v3.1.md');
|
|
12
|
+
if (fs.existsSync(batonDest)) {
|
|
13
|
+
console.log('\n ! BATON_v3.1.md already exists — skipping (delete it first to overwrite)');
|
|
14
|
+
} else {
|
|
15
|
+
fs.copyFileSync(path.join(TEMPLATES_DIR, 'BATON_v3.1.md'), batonDest);
|
|
16
|
+
results.push('Copied BATON_v3.1.md');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// 2. Copy skills/
|
|
20
|
+
const skillsSrc = path.join(TEMPLATES_DIR, 'skills');
|
|
21
|
+
const skillsDest = path.join(dest, 'skills');
|
|
22
|
+
|
|
23
|
+
// Always copy core/ and patterns/
|
|
24
|
+
copyDir(path.join(skillsSrc, 'core'), path.join(skillsDest, 'core'));
|
|
25
|
+
copyDir(path.join(skillsSrc, 'patterns'), path.join(skillsDest, 'patterns'));
|
|
26
|
+
|
|
27
|
+
// Create domains/ (empty, for user to add)
|
|
28
|
+
fs.mkdirSync(path.join(skillsDest, 'domains'), { recursive: true });
|
|
29
|
+
|
|
30
|
+
// Copy stack-specific skills (each is a directory with SKILL.md)
|
|
31
|
+
const stackSkills = STACK_MAP[stack] || [];
|
|
32
|
+
fs.mkdirSync(path.join(skillsDest, 'stacks'), { recursive: true });
|
|
33
|
+
for (const skillDir of stackSkills) {
|
|
34
|
+
const src = path.join(skillsSrc, 'stacks', skillDir);
|
|
35
|
+
if (fs.existsSync(src)) {
|
|
36
|
+
copyDir(src, path.join(skillsDest, 'stacks', skillDir));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const coreCount = fs.readdirSync(path.join(skillsDest, 'core')).filter(
|
|
41
|
+
f => fs.statSync(path.join(skillsDest, 'core', f)).isDirectory()
|
|
42
|
+
).length;
|
|
43
|
+
const stackCount = stackSkills.length;
|
|
44
|
+
const patternCount = fs.readdirSync(path.join(skillsDest, 'patterns')).filter(
|
|
45
|
+
f => fs.statSync(path.join(skillsDest, 'patterns', f)).isDirectory()
|
|
46
|
+
).length;
|
|
47
|
+
results.push(`Copied skills/ (${coreCount} core + ${stackCount} stack + ${patternCount} pattern)`);
|
|
48
|
+
|
|
49
|
+
// 3. Create .ai-rules/ with stub files
|
|
50
|
+
const aiRulesDir = path.join(dest, '.ai-rules');
|
|
51
|
+
fs.mkdirSync(aiRulesDir, { recursive: true });
|
|
52
|
+
const stubs = {
|
|
53
|
+
'project.md': `# Project Rules — ${projectName}\n\n> AI will fill this during discovery session.\n`,
|
|
54
|
+
'tech-stack.md': `# Tech Stack — ${projectName}\n\n> AI will fill this during discovery session.\n`,
|
|
55
|
+
'patterns.md': `# Patterns & Quirks — ${projectName}\n\n> AI adds entries here as it discovers gotchas and solutions.\n`,
|
|
56
|
+
'structure.md': `# Project Structure — ${projectName}\n\n> AI will fill this once the project scaffolding is set up.\n`,
|
|
57
|
+
};
|
|
58
|
+
for (const [file, content] of Object.entries(stubs)) {
|
|
59
|
+
fs.writeFileSync(path.join(aiRulesDir, file), content);
|
|
60
|
+
}
|
|
61
|
+
results.push('Created .ai-rules/ (4 stub files)');
|
|
62
|
+
|
|
63
|
+
// 4. Create handoff/ with .gitkeep
|
|
64
|
+
const handoffDir = path.join(dest, 'handoff');
|
|
65
|
+
fs.mkdirSync(handoffDir, { recursive: true });
|
|
66
|
+
fs.writeFileSync(path.join(handoffDir, '.gitkeep'), '');
|
|
67
|
+
results.push('Created handoff/');
|
|
68
|
+
|
|
69
|
+
// 5. Create IDE config file from template
|
|
70
|
+
const ide = IDE_MAP[tool] || IDE_MAP['Other'];
|
|
71
|
+
const stackLabel = STACK_LABEL[stack] || '';
|
|
72
|
+
|
|
73
|
+
if (ide.template) {
|
|
74
|
+
// Template-based IDE config (Claude Code, Cursor, Windsurf, etc.)
|
|
75
|
+
const templatePath = path.join(TEMPLATES_DIR, 'ide', ide.template);
|
|
76
|
+
let templateContent = fs.readFileSync(templatePath, 'utf-8');
|
|
77
|
+
|
|
78
|
+
templateContent = templateContent
|
|
79
|
+
.replace(/\{\{PROJECT_NAME\}\}/g, projectName)
|
|
80
|
+
.replace(/\{\{STACK\}\}/g, stackLabel)
|
|
81
|
+
.replace(/\{\{CURRENT_SESSION\}\}/g, '1')
|
|
82
|
+
.replace(/\{\{NEXT_SESSION\}\}/g, '2')
|
|
83
|
+
.replace(/\{\{BUILD_COMMAND\}\}/g, 'npm run build')
|
|
84
|
+
.replace(/\{\{DEV_COMMAND\}\}/g, 'npm run dev')
|
|
85
|
+
.replace(/\{\{TYPECHECK_COMMAND\}\}/g, 'npx tsc --noEmit')
|
|
86
|
+
.replace(/\{\{PROJECT_RULES\}\}/g, '<!-- AI will add project-specific rules here -->');
|
|
87
|
+
|
|
88
|
+
fs.writeFileSync(path.join(dest, ide.file), templateContent);
|
|
89
|
+
results.push(`Created ${ide.file}`);
|
|
90
|
+
} else {
|
|
91
|
+
// Codex — uses AGENTS.md directly (already generated in step 6)
|
|
92
|
+
results.push('Using AGENTS.md as IDE config (Codex)');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 6. Generate AGENTS.md (universal standard for AI coding agents)
|
|
96
|
+
const agentsMd = generateAgentsMd(projectName, stackLabel);
|
|
97
|
+
fs.writeFileSync(path.join(dest, 'AGENTS.md'), agentsMd);
|
|
98
|
+
results.push('Generated AGENTS.md');
|
|
99
|
+
|
|
100
|
+
// 7. Create PROGRESS.md, BACKLOG.md, FEATURES.md
|
|
101
|
+
fs.writeFileSync(path.join(dest, 'PROGRESS.md'),
|
|
102
|
+
`# Progress — ${projectName}\n\n## Sessions\n\n_No sessions yet. AI will log progress here._\n`);
|
|
103
|
+
fs.writeFileSync(path.join(dest, 'BACKLOG.md'),
|
|
104
|
+
`# Backlog — ${projectName}\n\n_Deferred items go here. AI adds items during sessions._\n`);
|
|
105
|
+
fs.writeFileSync(path.join(dest, 'FEATURES.md'),
|
|
106
|
+
`# Features — ${projectName}\n\n_User-facing feature documentation. AI updates this as features ship._\n`);
|
|
107
|
+
results.push('Created PROGRESS.md, BACKLOG.md, FEATURES.md');
|
|
108
|
+
|
|
109
|
+
return results;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function generateAgentsMd(projectName, stack) {
|
|
113
|
+
const stackLine = stack ? `- **Stack:** ${stack}` : '- **Stack:** (to be determined during Session Zero)';
|
|
114
|
+
return `# AGENTS.md
|
|
115
|
+
|
|
116
|
+
This file provides guidance to AI coding agents working with this repository.
|
|
117
|
+
|
|
118
|
+
## Project Overview
|
|
119
|
+
|
|
120
|
+
${projectName} — bootstrapped with the Baton protocol.
|
|
121
|
+
|
|
122
|
+
${stackLine}
|
|
123
|
+
- **Protocol:** Baton v3.1 (AI orchestration protocol)
|
|
124
|
+
- **Architecture:** See \`.ai-rules/\` for project context (generated during Session Zero)
|
|
125
|
+
|
|
126
|
+
## Getting Started
|
|
127
|
+
|
|
128
|
+
This project uses the Baton protocol for AI-assisted development.
|
|
129
|
+
|
|
130
|
+
1. Read \`BATON_v3.1.md\` — the orchestration protocol
|
|
131
|
+
2. Read \`.ai-rules/\` — project context files (AI-generated)
|
|
132
|
+
3. Read \`skills/\` — curated best practices for this stack
|
|
133
|
+
4. Check \`handoff/\` — session handoff files for continuity
|
|
134
|
+
|
|
135
|
+
## Build & Development Commands
|
|
136
|
+
|
|
137
|
+
\`\`\`bash
|
|
138
|
+
# Install dependencies
|
|
139
|
+
npm install
|
|
140
|
+
|
|
141
|
+
# Development
|
|
142
|
+
npm run dev
|
|
143
|
+
|
|
144
|
+
# Build
|
|
145
|
+
npm run build
|
|
146
|
+
\`\`\`
|
|
147
|
+
|
|
148
|
+
## Code Style & Conventions
|
|
149
|
+
|
|
150
|
+
See \`.ai-rules/patterns.md\` for project-specific patterns.
|
|
151
|
+
See \`skills/core/\` for universal rules (security, testing, anti-overengineering).
|
|
152
|
+
|
|
153
|
+
## Project Structure
|
|
154
|
+
|
|
155
|
+
\`\`\`
|
|
156
|
+
BATON_v3.1.md # AI orchestration protocol
|
|
157
|
+
.ai-rules/ # Project context (AI-generated)
|
|
158
|
+
skills/ # Best practice skills
|
|
159
|
+
core/ # Universal rules
|
|
160
|
+
stacks/ # Stack-specific patterns
|
|
161
|
+
patterns/ # Implementation patterns
|
|
162
|
+
handoff/ # Session handoff files
|
|
163
|
+
PROGRESS.md # Session progress log
|
|
164
|
+
BACKLOG.md # Deferred items
|
|
165
|
+
\`\`\`
|
|
166
|
+
|
|
167
|
+
## Agent Boundaries
|
|
168
|
+
|
|
169
|
+
This project follows the Baton protocol. Key rules:
|
|
170
|
+
- Read BATON_v3.1.md before starting work
|
|
171
|
+
- Check skills/ before web searching
|
|
172
|
+
- Document discoveries in .ai-rules/patterns.md
|
|
173
|
+
- Create handoff files at session end
|
|
174
|
+
- Update PROGRESS.md after each session
|
|
175
|
+
`;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function copyDir(src, dest) {
|
|
179
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
180
|
+
if (!fs.existsSync(src)) return;
|
|
181
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
182
|
+
for (const entry of entries) {
|
|
183
|
+
const srcPath = path.join(src, entry.name);
|
|
184
|
+
const destPath = path.join(dest, entry.name);
|
|
185
|
+
if (entry.isDirectory()) {
|
|
186
|
+
copyDir(srcPath, destPath);
|
|
187
|
+
} else {
|
|
188
|
+
fs.copyFileSync(srcPath, destPath);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
module.exports = { scaffold };
|