sunpeak 0.5.18 → 0.5.20

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 CHANGED
@@ -36,7 +36,11 @@ Quickstart, build, and test your ChatGPT App locally with OpenAI apps-sdk-ui Rea
36
36
  Requirements: Node (20+), pnpm (10+)
37
37
 
38
38
  ```bash
39
- pnpm dlx sunpeak new
39
+ # Install the CLI globally
40
+ pnpm add -g sunpeak
41
+
42
+ # Create a new project
43
+ sunpeak new
40
44
  ```
41
45
 
42
46
  To add sunpeak to an existing project, refer to the [documentation](https://docs.sunpeak.ai/add-to-existing-project).
package/bin/sunpeak.js CHANGED
@@ -4,8 +4,10 @@ import { existsSync, mkdirSync, cpSync, readFileSync, writeFileSync, renameSync
4
4
  import { join, dirname, basename } from 'path';
5
5
  import { fileURLToPath } from 'url';
6
6
  import { createInterface } from 'readline';
7
+ import { spawn } from 'child_process';
7
8
 
8
9
  const __dirname = dirname(fileURLToPath(import.meta.url));
10
+ const CLI_DIR = join(__dirname, '..', 'cli');
9
11
 
10
12
  function prompt(question) {
11
13
  const rl = createInterface({ input: process.stdin, output: process.stdout });
@@ -17,6 +19,28 @@ function prompt(question) {
17
19
  });
18
20
  }
19
21
 
22
+ function runCommand(command, args = [], options = {}) {
23
+ const child = spawn(command, args, {
24
+ cwd: process.cwd(),
25
+ stdio: 'inherit',
26
+ env: { ...process.env, FORCE_COLOR: '1' },
27
+ ...options,
28
+ });
29
+
30
+ child.on('exit', (code) => {
31
+ process.exit(code || 0);
32
+ });
33
+ }
34
+
35
+ function checkPackageJson() {
36
+ const pkgPath = join(process.cwd(), 'package.json');
37
+ if (!existsSync(pkgPath)) {
38
+ console.error('Error: No package.json found in current directory.');
39
+ console.error('Make sure you are in a Sunpeak project directory.');
40
+ process.exit(1);
41
+ }
42
+ }
43
+
20
44
  async function init(projectName) {
21
45
  if (!projectName) {
22
46
  projectName = await prompt('☀️ 🏔️ Project name [my-app]: ');
@@ -81,8 +105,17 @@ async function init(projectName) {
81
105
  console.log(`
82
106
  Done! To get started:
83
107
 
108
+ # Install the CLI (if not already installed)
109
+ pnpm add -g sunpeak
110
+
111
+ # Navigate to your project and install dependencies
84
112
  cd ${projectName}
85
- pnpm install && pnpm dev
113
+ pnpm install
114
+
115
+ # Start development
116
+ sunpeak dev
117
+
118
+ Alternatively, use "pnpm dlx sunpeak dev" if you prefer not to install globally.
86
119
 
87
120
  See README.md for more details.
88
121
  `);
@@ -90,13 +123,100 @@ See README.md for more details.
90
123
 
91
124
  const [, , command, ...args] = process.argv;
92
125
 
93
- if (command === 'new') {
94
- init(args[0]);
95
- } else {
96
- console.log(`
97
- sunpeak - The MCP App SDK
126
+ // Main CLI handler
127
+ (async () => {
128
+ // Commands that don't require a package.json
129
+ const standaloneCommands = ['new', 'help', undefined];
130
+
131
+ if (command && !standaloneCommands.includes(command)) {
132
+ checkPackageJson();
133
+ }
134
+
135
+ switch (command) {
136
+ case 'new':
137
+ await init(args[0]);
138
+ break;
139
+
140
+ case 'dev':
141
+ runCommand('pnpm', ['dev', ...args]);
142
+ break;
143
+
144
+ case 'build':
145
+ {
146
+ const { build } = await import(join(CLI_DIR, 'build.mjs'));
147
+ await build(process.cwd());
148
+ }
149
+ break;
150
+
151
+ case 'mcp':
152
+ case 'mcp:serve':
153
+ if (command === 'mcp:serve' || args[0] === 'serve' || args[0] === ':serve') {
154
+ runCommand('pnpm', ['mcp:serve', ...(command === 'mcp:serve' ? args : args.slice(1))]);
155
+ } else {
156
+ runCommand('pnpm', ['mcp', ...args]);
157
+ }
158
+ break;
159
+
160
+ case 'lint':
161
+ runCommand('pnpm', ['lint', ...args]);
162
+ break;
163
+
164
+ case 'typecheck':
165
+ runCommand('pnpm', ['typecheck', ...args]);
166
+ break;
167
+
168
+ case 'test':
169
+ runCommand('pnpm', ['test', ...args]);
170
+ break;
171
+
172
+ case 'format':
173
+ runCommand('pnpm', ['format', ...args]);
174
+ break;
175
+
176
+ case 'validate':
177
+ {
178
+ const { validate } = await import(join(CLI_DIR, 'validate.mjs'));
179
+ await validate(process.cwd());
180
+ }
181
+ break;
182
+
183
+ case 'help':
184
+ case undefined:
185
+ console.log(`
186
+ ☀️ 🏔️ sunpeak - The MCP App SDK
187
+
188
+ Usage:
189
+ sunpeak <command> [options]
98
190
 
99
191
  Commands:
100
- new [name] Create a new project from template
192
+ new [name] Create a new project from template
193
+ dev Start the development server
194
+ build Build all resources for production
195
+ mcp Run the MCP server with nodemon
196
+ mcp:serve Run the MCP server directly
197
+ lint Run ESLint to check code quality
198
+ typecheck Run TypeScript type checking
199
+ test Run tests with Vitest
200
+ format Format code with Prettier
201
+ validate Run full validation suite
202
+ help Show this help message
203
+
204
+ Examples:
205
+ sunpeak new my-app
206
+ sunpeak dev
207
+ sunpeak build
208
+ sunpeak mcp
209
+
210
+ For more information, visit: https://sunpeak.ai/
101
211
  `);
102
- }
212
+ break;
213
+
214
+ default:
215
+ console.error(`Unknown command: ${command}`);
216
+ console.error('Run "sunpeak help" to see available commands.');
217
+ process.exit(1);
218
+ }
219
+ })().catch((error) => {
220
+ console.error('Error:', error.message);
221
+ process.exit(1);
222
+ });
package/cli/build.mjs ADDED
@@ -0,0 +1,199 @@
1
+ #!/usr/bin/env node
2
+ import { execSync } from 'child_process';
3
+ import { existsSync, rmSync, readdirSync, readFileSync, writeFileSync, mkdirSync, copyFileSync } from 'fs';
4
+ import path from 'path';
5
+
6
+ /**
7
+ * Detect package manager for the project
8
+ */
9
+ function detectPackageManager(projectRoot) {
10
+ if (existsSync(path.join(projectRoot, 'pnpm-lock.yaml'))) return 'pnpm';
11
+ if (existsSync(path.join(projectRoot, 'yarn.lock'))) return 'yarn';
12
+ if (existsSync(path.join(projectRoot, 'package-lock.json'))) return 'npm';
13
+ return 'pnpm'; // default
14
+ }
15
+
16
+ /**
17
+ * Build all resources for a Sunpeak project
18
+ * Runs in the context of a user's project directory
19
+ */
20
+ export async function build(projectRoot = process.cwd()) {
21
+ const pm = detectPackageManager(projectRoot);
22
+
23
+ // Check for package.json first
24
+ const pkgJsonPath = path.join(projectRoot, 'package.json');
25
+ if (!existsSync(pkgJsonPath)) {
26
+ console.error('Error: No package.json found in current directory');
27
+ console.error('Make sure you are in a Sunpeak project directory');
28
+ process.exit(1);
29
+ }
30
+
31
+ const distDir = path.join(projectRoot, 'dist/chatgpt');
32
+ const buildDir = path.join(projectRoot, 'dist/build-output');
33
+ const tempDir = path.join(projectRoot, '.tmp');
34
+ const resourcesDir = path.join(projectRoot, 'src/components/resources');
35
+ const templateFile = path.join(projectRoot, 'src/index-resource.tsx');
36
+ const viteConfigFile = path.join(projectRoot, 'vite.config.build.ts');
37
+
38
+ // Validate project structure
39
+ if (!existsSync(resourcesDir)) {
40
+ console.error('Error: src/components/resources directory not found');
41
+ console.error('Expected location: ' + resourcesDir);
42
+ console.error('\nThe build command expects the standard Sunpeak project structure.');
43
+ console.error('If you have customized your project structure, you may need to use');
44
+ console.error('a custom build script instead of "sunpeak build".');
45
+ process.exit(1);
46
+ }
47
+
48
+ if (!existsSync(templateFile)) {
49
+ console.error('Error: src/index-resource.tsx not found');
50
+ console.error('Expected location: ' + templateFile);
51
+ console.error('\nThis file is the template entry point for building resources.');
52
+ console.error('If you have moved or renamed it, you may need to use a custom build script.');
53
+ process.exit(1);
54
+ }
55
+
56
+ if (!existsSync(viteConfigFile)) {
57
+ console.error('Error: vite.config.build.ts not found');
58
+ console.error('Expected location: ' + viteConfigFile);
59
+ console.error('\nThis Vite config is required for building resources.');
60
+ console.error('If you have renamed it, you may need to use a custom build script.');
61
+ process.exit(1);
62
+ }
63
+
64
+ // Clean dist and temp directories
65
+ if (existsSync(distDir)) {
66
+ rmSync(distDir, { recursive: true });
67
+ }
68
+ if (existsSync(tempDir)) {
69
+ rmSync(tempDir, { recursive: true });
70
+ }
71
+ mkdirSync(distDir, { recursive: true });
72
+ mkdirSync(tempDir, { recursive: true });
73
+
74
+ // Auto-discover all resources
75
+ const resourceFiles = readdirSync(resourcesDir)
76
+ .filter(file => file.endsWith('-resource.tsx'))
77
+ .map(file => {
78
+ // Extract kebab-case name: 'counter-resource.tsx' -> 'counter'
79
+ const kebabName = file.replace('-resource.tsx', '');
80
+
81
+ // Convert kebab-case to PascalCase: 'counter' -> 'Counter', 'my-widget' -> 'MyWidget'
82
+ const pascalName = kebabName
83
+ .split('-')
84
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
85
+ .join('');
86
+
87
+ return {
88
+ componentName: `${pascalName}Resource`,
89
+ componentFile: file.replace('.tsx', ''),
90
+ entry: `.tmp/index-${kebabName}.tsx`,
91
+ output: `${kebabName}.js`,
92
+ buildOutDir: path.join(buildDir, kebabName),
93
+ };
94
+ });
95
+
96
+ if (resourceFiles.length === 0) {
97
+ console.error('Error: No resource files found in src/components/resources/');
98
+ console.error('Resource files should be named like: counter-resource.tsx');
99
+ process.exit(1);
100
+ }
101
+
102
+ console.log('Building all resources...\n');
103
+
104
+ // Read and validate the template
105
+ const template = readFileSync(templateFile, 'utf-8');
106
+
107
+ // Verify template has required placeholders
108
+ if (!template.includes('// RESOURCE_IMPORT')) {
109
+ console.error('Error: src/index-resource.tsx is missing "// RESOURCE_IMPORT" placeholder');
110
+ console.error('\nThe template file must include this comment where the resource import should go.');
111
+ console.error('If you have customized this file, ensure it has the required placeholders.');
112
+ process.exit(1);
113
+ }
114
+
115
+ if (!template.includes('// RESOURCE_MOUNT')) {
116
+ console.error('Error: src/index-resource.tsx is missing "// RESOURCE_MOUNT" placeholder');
117
+ console.error('\nThe template file must include this comment where the resource mount should go.');
118
+ console.error('If you have customized this file, ensure it has the required placeholders.');
119
+ process.exit(1);
120
+ }
121
+
122
+ // Build all resources (but don't copy yet)
123
+ resourceFiles.forEach(({ componentName, componentFile, entry, output, buildOutDir }, index) => {
124
+ console.log(`[${index + 1}/${resourceFiles.length}] Building ${output}...`);
125
+
126
+ try {
127
+ // Create build directory if it doesn't exist
128
+ if (!existsSync(buildOutDir)) {
129
+ mkdirSync(buildOutDir, { recursive: true });
130
+ }
131
+
132
+ // Create entry file from template in temp directory
133
+ const entryContent = template
134
+ .replace('// RESOURCE_IMPORT', `import { ${componentName} } from '../src/components/resources/${componentFile}';`)
135
+ .replace('// RESOURCE_MOUNT', `createRoot(root).render(<${componentName} />);`);
136
+
137
+ const entryPath = path.join(projectRoot, entry);
138
+ writeFileSync(entryPath, entryContent);
139
+
140
+ // Build with vite to build directory
141
+ const viteCommand = pm === 'npm' ? 'npx vite' : `${pm} exec vite`;
142
+ execSync(
143
+ `${viteCommand} build --config vite.config.build.ts`,
144
+ {
145
+ cwd: projectRoot,
146
+ stdio: 'inherit',
147
+ env: {
148
+ ...process.env,
149
+ ENTRY_FILE: entry,
150
+ OUTPUT_FILE: output,
151
+ OUT_DIR: buildOutDir,
152
+ },
153
+ }
154
+ );
155
+ } catch (error) {
156
+ console.error(`Failed to build ${output}`);
157
+ process.exit(1);
158
+ }
159
+ });
160
+
161
+ // Now copy all files from build-output to dist/chatgpt
162
+ console.log('\nCopying built files to dist/chatgpt...');
163
+ resourceFiles.forEach(({ output, buildOutDir }) => {
164
+ const builtFile = path.join(buildOutDir, output);
165
+ const destFile = path.join(distDir, output);
166
+
167
+ if (existsSync(builtFile)) {
168
+ copyFileSync(builtFile, destFile);
169
+ console.log(`✓ Copied ${output}`);
170
+ } else {
171
+ console.error(`Built file not found: ${builtFile}`);
172
+ if (existsSync(buildOutDir)) {
173
+ console.log(` Files in ${buildOutDir}:`, readdirSync(buildOutDir));
174
+ } else {
175
+ console.log(` Build directory doesn't exist: ${buildOutDir}`);
176
+ }
177
+ process.exit(1);
178
+ }
179
+ });
180
+
181
+ // Clean up temp and build directories
182
+ if (existsSync(tempDir)) {
183
+ rmSync(tempDir, { recursive: true });
184
+ }
185
+ if (existsSync(buildDir)) {
186
+ rmSync(buildDir, { recursive: true });
187
+ }
188
+
189
+ console.log('\n✓ All resources built successfully!');
190
+ console.log(`\nBuilt files:`, readdirSync(distDir));
191
+ }
192
+
193
+ // Allow running directly
194
+ if (import.meta.url === `file://${process.argv[1]}`) {
195
+ build().catch(error => {
196
+ console.error(error);
197
+ process.exit(1);
198
+ });
199
+ }
@@ -0,0 +1,245 @@
1
+ #!/usr/bin/env node
2
+ import { execSync, spawn } from 'child_process';
3
+ import { existsSync, readdirSync, readFileSync } from 'fs';
4
+ import { join } from 'path';
5
+ import http from 'http';
6
+
7
+ /**
8
+ * Validate a Sunpeak project by running all checks
9
+ * Runs in the context of a user's project directory
10
+ */
11
+
12
+ // Color codes for output
13
+ const colors = {
14
+ red: '\x1b[0;31m',
15
+ green: '\x1b[0;32m',
16
+ blue: '\x1b[0;34m',
17
+ yellow: '\x1b[1;33m',
18
+ reset: '\x1b[0m',
19
+ };
20
+
21
+ function printSuccess(text) {
22
+ console.log(`${colors.green}✓ ${text}${colors.reset}`);
23
+ }
24
+
25
+ function printError(text) {
26
+ console.log(`${colors.red}✗ ${text}${colors.reset}`);
27
+ }
28
+
29
+ function printWarning(text) {
30
+ console.log(`${colors.yellow}⚠ ${text}${colors.reset}`);
31
+ }
32
+
33
+ function detectPackageManager(projectRoot) {
34
+ if (existsSync(join(projectRoot, 'pnpm-lock.yaml'))) return 'pnpm';
35
+ if (existsSync(join(projectRoot, 'yarn.lock'))) return 'yarn';
36
+ if (existsSync(join(projectRoot, 'package-lock.json'))) return 'npm';
37
+ return 'pnpm'; // default
38
+ }
39
+
40
+ function hasScript(projectRoot, scriptName) {
41
+ try {
42
+ const pkgJson = JSON.parse(readFileSync(join(projectRoot, 'package.json'), 'utf-8'));
43
+ return !!pkgJson.scripts?.[scriptName];
44
+ } catch (error) {
45
+ return false;
46
+ }
47
+ }
48
+
49
+ function runCommand(command, cwd, pm = 'pnpm') {
50
+ try {
51
+ // Replace pnpm with detected package manager
52
+ const actualCommand = command.replace(/^pnpm/, pm);
53
+ execSync(actualCommand, {
54
+ cwd,
55
+ stdio: 'inherit',
56
+ env: { ...process.env, FORCE_COLOR: '1' },
57
+ });
58
+ return true;
59
+ } catch (error) {
60
+ return false;
61
+ }
62
+ }
63
+
64
+ function waitForServer(port, timeout = 10000) {
65
+ return new Promise((resolve, reject) => {
66
+ const startTime = Date.now();
67
+ const checkServer = () => {
68
+ const req = http.get(`http://localhost:${port}`, () => {
69
+ resolve();
70
+ });
71
+ req.on('error', () => {
72
+ if (Date.now() - startTime > timeout) {
73
+ reject(new Error(`Server did not start within ${timeout}ms`));
74
+ } else {
75
+ setTimeout(checkServer, 500);
76
+ }
77
+ });
78
+ req.end();
79
+ };
80
+ checkServer();
81
+ });
82
+ }
83
+
84
+ export async function validate(projectRoot = process.cwd()) {
85
+ const pm = detectPackageManager(projectRoot);
86
+
87
+ // Check package.json exists
88
+ if (!existsSync(join(projectRoot, 'package.json'))) {
89
+ console.error('Error: No package.json found in current directory');
90
+ console.error('Make sure you are in a Sunpeak project directory');
91
+ process.exit(1);
92
+ }
93
+
94
+ console.log(`${colors.yellow}Starting validation for Sunpeak project...${colors.reset}`);
95
+ console.log(`Project root: ${projectRoot}`);
96
+ console.log(`Package manager: ${pm}\n`);
97
+
98
+ try {
99
+ console.log(`Running: ${pm} install`);
100
+ if (!runCommand(`${pm} install`, projectRoot, pm)) {
101
+ throw new Error(`${pm} install failed`);
102
+ }
103
+ console.log()
104
+ printSuccess(`${pm} install`);
105
+
106
+ // Format (optional)
107
+ if (hasScript(projectRoot, 'format')) {
108
+ console.log(`\nRunning: ${pm} format`);
109
+ if (!runCommand(`${pm} format`, projectRoot, pm)) {
110
+ throw new Error(`${pm} format failed`);
111
+ }
112
+ printSuccess(`${pm} format`);
113
+ } else {
114
+ printWarning('Skipping format: no "format" script in package.json');
115
+ }
116
+
117
+ // Lint (optional)
118
+ if (hasScript(projectRoot, 'lint')) {
119
+ console.log(`\nRunning: ${pm} lint`);
120
+ if (!runCommand(`${pm} lint`, projectRoot, pm)) {
121
+ throw new Error(`${pm} lint failed`);
122
+ }
123
+ printSuccess(`${pm} lint`);
124
+ } else {
125
+ printWarning('Skipping lint: no "lint" script in package.json');
126
+ }
127
+
128
+ // Typecheck (optional)
129
+ if (hasScript(projectRoot, 'typecheck')) {
130
+ console.log(`\nRunning: ${pm} typecheck`);
131
+ if (!runCommand(`${pm} typecheck`, projectRoot, pm)) {
132
+ throw new Error(`${pm} typecheck failed`);
133
+ }
134
+ printSuccess(`${pm} typecheck`);
135
+ } else {
136
+ printWarning('Skipping typecheck: no "typecheck" script in package.json');
137
+ }
138
+
139
+ // Test (optional)
140
+ if (hasScript(projectRoot, 'test')) {
141
+ console.log(`\nRunning: ${pm} test`);
142
+ if (!runCommand(`${pm} test`, projectRoot, pm)) {
143
+ throw new Error(`${pm} test failed`);
144
+ }
145
+ printSuccess(`${pm} test`);
146
+ } else {
147
+ printWarning('Skipping test: no "test" script in package.json');
148
+ }
149
+
150
+ console.log(`\nRunning: sunpeak build`);
151
+ // Import and run build directly
152
+ const { build } = await import('./build.mjs');
153
+ await build(projectRoot);
154
+
155
+ const chatgptDir = join(projectRoot, 'dist', 'chatgpt');
156
+
157
+ if (!existsSync(chatgptDir)) {
158
+ printError('dist/chatgpt directory not found after build');
159
+ process.exit(1);
160
+ }
161
+
162
+ const files = readdirSync(chatgptDir);
163
+ if (files.length === 0) {
164
+ printError('No files found in dist/chatgpt/');
165
+ process.exit(1);
166
+ }
167
+
168
+ // Verify all files are .js files
169
+ const nonJsFiles = files.filter(f => !f.endsWith('.js'));
170
+ if (nonJsFiles.length > 0) {
171
+ printError(`Unexpected non-JS files in ./dist/chatgpt/: ${nonJsFiles.join(', ')}`);
172
+ process.exit(1);
173
+ }
174
+
175
+ console.log()
176
+ printSuccess('sunpeak build');
177
+
178
+ // MCP Server Check (optional)
179
+ if (hasScript(projectRoot, 'mcp:serve')) {
180
+ console.log(`\nRunning: ${pm} mcp:serve`);
181
+ const mcpProcess = spawn(pm, ['mcp:serve'], {
182
+ cwd: projectRoot,
183
+ stdio: ['ignore', 'pipe', 'pipe'],
184
+ env: { ...process.env, FORCE_COLOR: '1' },
185
+ });
186
+
187
+ const mcpErrors = [];
188
+
189
+ mcpProcess.stderr.on('data', (data) => {
190
+ const message = data.toString();
191
+ if (message.includes('error') || message.includes('Error')) {
192
+ mcpErrors.push(message.trim());
193
+ }
194
+ });
195
+
196
+ // Store process for cleanup
197
+ process.on('exit', () => {
198
+ if (mcpProcess && !mcpProcess.killed) {
199
+ mcpProcess.kill();
200
+ }
201
+ });
202
+
203
+ try {
204
+ console.log('\nWaiting for MCP server to start on port 6766...');
205
+ await waitForServer(6766, 10000);
206
+
207
+ // Give it a moment to ensure no immediate errors
208
+ await new Promise(resolve => setTimeout(resolve, 1000));
209
+
210
+ if (mcpErrors.length > 0) {
211
+ printError('MCP server started but reported errors:');
212
+ mcpErrors.forEach(err => console.log(` ${err}`));
213
+ throw new Error('MCP server has errors');
214
+ }
215
+
216
+ } catch (error) {
217
+ printError(`MCP server failed to start: ${error.message}`);
218
+ throw error;
219
+ } finally {
220
+ console.log('Stopping MCP server...');
221
+ mcpProcess.kill();
222
+ // Give it a moment to shut down
223
+ await new Promise(resolve => setTimeout(resolve, 1000));
224
+ }
225
+ console.log()
226
+ printSuccess(`${pm} mcp\n`);
227
+ } else {
228
+ printWarning('Skipping MCP server check: no "mcp:serve" script in package.json\n');
229
+ }
230
+
231
+ printSuccess('All systems GO!\n\n');
232
+ process.exit(0);
233
+ } catch (error) {
234
+ console.error(`\n${colors.red}Error: ${error.message}${colors.reset}\n`);
235
+ process.exit(1);
236
+ }
237
+ }
238
+
239
+ // Allow running directly
240
+ if (import.meta.url === `file://${process.argv[1]}`) {
241
+ validate().catch(error => {
242
+ console.error(error);
243
+ process.exit(1);
244
+ });
245
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sunpeak",
3
- "version": "0.5.18",
3
+ "version": "0.5.20",
4
4
  "description": "The MCP App SDK. Quickstart, build, & test your ChatGPT App locally!",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -33,6 +33,7 @@
33
33
  "files": [
34
34
  "dist",
35
35
  "bin",
36
+ "cli",
36
37
  "template",
37
38
  "README.md"
38
39
  ],