@codihaus/claude-skills 1.0.0 → 1.2.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/knowledge/domains/_index.md +105 -0
- package/knowledge/domains/ecommerce/_index.md +499 -0
- package/knowledge/domains/saas/_index.md +371 -0
- package/knowledge/stacks/_index.md +101 -0
- package/knowledge/stacks/directus/_index.md +349 -0
- package/knowledge/stacks/nextjs/_index.md +654 -0
- package/knowledge/stacks/nuxt/_index.md +469 -0
- package/package.json +3 -1
- package/project-scripts/graph.py +330 -0
- package/skills/_registry.md +61 -0
- package/skills/dev-coding/SKILL.md +16 -5
- package/skills/dev-coding-backend/SKILL.md +116 -251
- package/skills/dev-coding-frontend/SKILL.md +134 -388
- package/skills/dev-review/SKILL.md +13 -2
- package/skills/dev-scout/SKILL.md +180 -2
- package/skills/dev-scout/references/stack-patterns.md +371 -0
- package/skills/dev-specs/SKILL.md +74 -2
- package/src/commands/init.js +89 -12
- package/src/utils/project-setup.js +444 -0
- package/src/utils/skills.js +87 -1
- /package/{skills/dev-coding-frontend/references/nextjs.md → knowledge/stacks/nextjs/references/performance.md} +0 -0
package/src/commands/init.js
CHANGED
|
@@ -20,7 +20,9 @@ import {
|
|
|
20
20
|
import {
|
|
21
21
|
getAvailableSkills,
|
|
22
22
|
getInstalledSkills,
|
|
23
|
-
copySkillsToProject
|
|
23
|
+
copySkillsToProject,
|
|
24
|
+
copyKnowledgeToProject,
|
|
25
|
+
copyScriptsToProject
|
|
24
26
|
} from '../utils/skills.js';
|
|
25
27
|
import {
|
|
26
28
|
setupSettings,
|
|
@@ -28,6 +30,11 @@ import {
|
|
|
28
30
|
setupClaudeMd,
|
|
29
31
|
updateGitignore
|
|
30
32
|
} from '../utils/config.js';
|
|
33
|
+
import {
|
|
34
|
+
runProjectSetup,
|
|
35
|
+
detectEnvExample,
|
|
36
|
+
hasEnvFile
|
|
37
|
+
} from '../utils/project-setup.js';
|
|
31
38
|
|
|
32
39
|
export async function init(options) {
|
|
33
40
|
const projectPath = process.cwd();
|
|
@@ -158,7 +165,41 @@ export async function init(options) {
|
|
|
158
165
|
process.exit(1);
|
|
159
166
|
}
|
|
160
167
|
|
|
161
|
-
// Step 5:
|
|
168
|
+
// Step 5: Copy knowledge (stacks, domains)
|
|
169
|
+
const knowledgeSpinner = ora('Installing knowledge base...').start();
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
const { copied: knowledgeCopied, errors: knowledgeErrors } = await copyKnowledgeToProject(projectPath);
|
|
173
|
+
|
|
174
|
+
if (knowledgeErrors.length > 0) {
|
|
175
|
+
knowledgeSpinner.warn(`Installed ${knowledgeCopied.length} knowledge folders with ${knowledgeErrors.length} errors`);
|
|
176
|
+
} else if (knowledgeCopied.length > 0) {
|
|
177
|
+
knowledgeSpinner.succeed(`Installed knowledge: ${knowledgeCopied.join(', ')}`);
|
|
178
|
+
} else {
|
|
179
|
+
knowledgeSpinner.info('No knowledge to install');
|
|
180
|
+
}
|
|
181
|
+
} catch (e) {
|
|
182
|
+
knowledgeSpinner.warn('Failed to install knowledge');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Step 6: Copy scripts (graph.py, etc.)
|
|
186
|
+
const scriptsSpinner = ora('Installing scripts...').start();
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
const { copied: scriptsCopied, errors: scriptsErrors } = await copyScriptsToProject(projectPath);
|
|
190
|
+
|
|
191
|
+
if (scriptsErrors.length > 0) {
|
|
192
|
+
scriptsSpinner.warn(`Installed ${scriptsCopied.length} scripts with ${scriptsErrors.length} errors`);
|
|
193
|
+
} else if (scriptsCopied.length > 0) {
|
|
194
|
+
scriptsSpinner.succeed(`Installed scripts: ${scriptsCopied.join(', ')}`);
|
|
195
|
+
} else {
|
|
196
|
+
scriptsSpinner.info('No scripts to install');
|
|
197
|
+
}
|
|
198
|
+
} catch (e) {
|
|
199
|
+
scriptsSpinner.warn('Failed to install scripts');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Step 7: Set up settings
|
|
162
203
|
const settingsSpinner = ora('Setting up Claude Code configuration...').start();
|
|
163
204
|
|
|
164
205
|
try {
|
|
@@ -168,7 +209,7 @@ export async function init(options) {
|
|
|
168
209
|
settingsSpinner.warn('Failed to set up settings');
|
|
169
210
|
}
|
|
170
211
|
|
|
171
|
-
// Step
|
|
212
|
+
// Step 8: Set up hooks
|
|
172
213
|
if (!options.noHooks) {
|
|
173
214
|
const hooksSpinner = ora('Setting up hooks...').start();
|
|
174
215
|
|
|
@@ -184,7 +225,7 @@ export async function init(options) {
|
|
|
184
225
|
}
|
|
185
226
|
}
|
|
186
227
|
|
|
187
|
-
// Step
|
|
228
|
+
// Step 9: Set up CLAUDE.md
|
|
188
229
|
const claudeSpinner = ora('Setting up CLAUDE.md...').start();
|
|
189
230
|
|
|
190
231
|
try {
|
|
@@ -194,7 +235,7 @@ export async function init(options) {
|
|
|
194
235
|
claudeSpinner.warn('Failed to set up CLAUDE.md');
|
|
195
236
|
}
|
|
196
237
|
|
|
197
|
-
// Step
|
|
238
|
+
// Step 10: Update .gitignore
|
|
198
239
|
const gitSpinner = ora('Updating .gitignore...').start();
|
|
199
240
|
|
|
200
241
|
try {
|
|
@@ -204,15 +245,21 @@ export async function init(options) {
|
|
|
204
245
|
gitSpinner.warn('Failed to update .gitignore');
|
|
205
246
|
}
|
|
206
247
|
|
|
207
|
-
// Step
|
|
248
|
+
// Step 11: Project setup (package manager, .env, node version)
|
|
249
|
+
console.log('\n' + chalk.cyan('─'.repeat(40)));
|
|
250
|
+
console.log(chalk.bold('📦 Project Setup\n'));
|
|
251
|
+
|
|
252
|
+
const setupResult = await runProjectSetup(projectPath, { yes: options.yes });
|
|
253
|
+
|
|
254
|
+
// Step 12: Check skill-specific project dependencies
|
|
208
255
|
if (!options.noDeps) {
|
|
209
256
|
console.log('');
|
|
210
|
-
const projectSpinner = ora('Checking
|
|
257
|
+
const projectSpinner = ora('Checking skill dependencies...').start();
|
|
211
258
|
const projectDeps = await checkProjectDeps(projectPath);
|
|
212
259
|
projectSpinner.stop();
|
|
213
260
|
|
|
214
261
|
if (projectDeps.missing.length > 0) {
|
|
215
|
-
console.log(chalk.yellow('\n⚠️ Some optional dependencies are missing:\n'));
|
|
262
|
+
console.log(chalk.yellow('\n⚠️ Some optional dependencies for skills are missing:\n'));
|
|
216
263
|
|
|
217
264
|
for (const dep of projectDeps.missing) {
|
|
218
265
|
const cmd = dep.type === 'pip'
|
|
@@ -226,7 +273,7 @@ export async function init(options) {
|
|
|
226
273
|
const { install } = await inquirer.prompt([{
|
|
227
274
|
type: 'confirm',
|
|
228
275
|
name: 'install',
|
|
229
|
-
message: 'Install missing dependencies now?',
|
|
276
|
+
message: 'Install missing skill dependencies now?',
|
|
230
277
|
default: false
|
|
231
278
|
}]);
|
|
232
279
|
|
|
@@ -240,11 +287,41 @@ export async function init(options) {
|
|
|
240
287
|
// Done!
|
|
241
288
|
console.log(chalk.green('\n✅ Claude Skills initialized successfully!\n'));
|
|
242
289
|
|
|
290
|
+
// Show startup commands if detected
|
|
291
|
+
if (setupResult.startupCommands && setupResult.startupCommands.length > 0) {
|
|
292
|
+
console.log(chalk.bold('Available commands:'));
|
|
293
|
+
console.log('');
|
|
294
|
+
for (const cmd of setupResult.startupCommands) {
|
|
295
|
+
const pm = setupResult.packageManager?.name || 'npm';
|
|
296
|
+
console.log(` ${chalk.cyan(`${pm} run ${cmd.name}`)} - ${chalk.gray(cmd.command)}`);
|
|
297
|
+
}
|
|
298
|
+
console.log('');
|
|
299
|
+
}
|
|
300
|
+
|
|
243
301
|
console.log(chalk.bold('Next steps:'));
|
|
244
302
|
console.log('');
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
303
|
+
|
|
304
|
+
// Dynamic next steps based on setup
|
|
305
|
+
let stepNum = 1;
|
|
306
|
+
|
|
307
|
+
if (!setupResult.depsInstalled && setupResult.packageManager) {
|
|
308
|
+
console.log(` ${stepNum}. Install dependencies: ${chalk.cyan(setupResult.packageManager.install)}`);
|
|
309
|
+
stepNum++;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (!setupResult.envCreated && setupResult.packageManager) {
|
|
313
|
+
const envExample = detectEnvExample(projectPath);
|
|
314
|
+
if (envExample && !hasEnvFile(projectPath)) {
|
|
315
|
+
console.log(` ${stepNum}. Set up environment: ${chalk.cyan(`cp ${envExample.file} .env`)} and fill in values`);
|
|
316
|
+
stepNum++;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
console.log(` ${stepNum}. Start Claude Code in this project`);
|
|
321
|
+
stepNum++;
|
|
322
|
+
console.log(` ${stepNum}. Try a skill: ${chalk.cyan('/debrief "your project idea"')}`);
|
|
323
|
+
stepNum++;
|
|
324
|
+
console.log(` ${stepNum}. See all skills: ${chalk.cyan('/help')}`);
|
|
248
325
|
console.log('');
|
|
249
326
|
console.log(chalk.gray('Docs: .claude/skills/_registry.md'));
|
|
250
327
|
console.log('');
|
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Setup Utilities
|
|
3
|
+
*
|
|
4
|
+
* Detects and helps set up the development environment.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { execSync } from 'child_process';
|
|
8
|
+
import fs from 'fs-extra';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import chalk from 'chalk';
|
|
11
|
+
import inquirer from 'inquirer';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Detect package manager from lockfile
|
|
15
|
+
*/
|
|
16
|
+
export function detectPackageManager(projectPath) {
|
|
17
|
+
const lockfiles = {
|
|
18
|
+
'bun.lockb': { name: 'bun', install: 'bun install' },
|
|
19
|
+
'pnpm-lock.yaml': { name: 'pnpm', install: 'pnpm install' },
|
|
20
|
+
'yarn.lock': { name: 'yarn', install: 'yarn install' },
|
|
21
|
+
'package-lock.json': { name: 'npm', install: 'npm install' }
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
for (const [file, pm] of Object.entries(lockfiles)) {
|
|
25
|
+
if (fs.existsSync(path.join(projectPath, file))) {
|
|
26
|
+
return pm;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Default to npm if package.json exists
|
|
31
|
+
if (fs.existsSync(path.join(projectPath, 'package.json'))) {
|
|
32
|
+
return { name: 'npm', install: 'npm install' };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Check if node_modules exists
|
|
40
|
+
*/
|
|
41
|
+
export function hasNodeModules(projectPath) {
|
|
42
|
+
return fs.existsSync(path.join(projectPath, 'node_modules'));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Install project dependencies
|
|
47
|
+
*/
|
|
48
|
+
export async function installDeps(projectPath, pm) {
|
|
49
|
+
try {
|
|
50
|
+
execSync(pm.install, {
|
|
51
|
+
cwd: projectPath,
|
|
52
|
+
stdio: 'inherit'
|
|
53
|
+
});
|
|
54
|
+
return true;
|
|
55
|
+
} catch (e) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Detect node version requirement
|
|
62
|
+
*/
|
|
63
|
+
export function detectNodeVersion(projectPath) {
|
|
64
|
+
const versionFiles = ['.nvmrc', '.node-version', '.tool-versions'];
|
|
65
|
+
|
|
66
|
+
for (const file of versionFiles) {
|
|
67
|
+
const filePath = path.join(projectPath, file);
|
|
68
|
+
if (fs.existsSync(filePath)) {
|
|
69
|
+
const content = fs.readFileSync(filePath, 'utf-8').trim();
|
|
70
|
+
|
|
71
|
+
// Handle .tool-versions format (asdf)
|
|
72
|
+
if (file === '.tool-versions') {
|
|
73
|
+
const match = content.match(/nodejs\s+(\S+)/);
|
|
74
|
+
if (match) {
|
|
75
|
+
return { file, version: match[1] };
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
// .nvmrc or .node-version - just the version
|
|
79
|
+
const version = content.replace(/^v/, '');
|
|
80
|
+
return { file, version };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Check package.json engines
|
|
86
|
+
const pkgPath = path.join(projectPath, 'package.json');
|
|
87
|
+
if (fs.existsSync(pkgPath)) {
|
|
88
|
+
const pkg = fs.readJsonSync(pkgPath);
|
|
89
|
+
if (pkg.engines?.node) {
|
|
90
|
+
return { file: 'package.json (engines)', version: pkg.engines.node };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Get current node version
|
|
99
|
+
*/
|
|
100
|
+
export function getCurrentNodeVersion() {
|
|
101
|
+
try {
|
|
102
|
+
return process.version.replace(/^v/, '');
|
|
103
|
+
} catch (e) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Check if current node version satisfies requirement
|
|
110
|
+
*/
|
|
111
|
+
export function checkNodeVersion(required, current) {
|
|
112
|
+
// Simple check - just compare major versions for now
|
|
113
|
+
const reqMajor = parseInt(required.split('.')[0]);
|
|
114
|
+
const curMajor = parseInt(current.split('.')[0]);
|
|
115
|
+
|
|
116
|
+
if (isNaN(reqMajor)) {
|
|
117
|
+
// Could be a range like >=18, just warn
|
|
118
|
+
return { ok: true, warning: `Required: ${required}, Current: ${current}` };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (curMajor < reqMajor) {
|
|
122
|
+
return { ok: false, warning: `Required: ${required}, Current: ${current}` };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return { ok: true };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Detect .env.example and parse variables
|
|
130
|
+
*/
|
|
131
|
+
export function detectEnvExample(projectPath) {
|
|
132
|
+
const envExampleNames = ['.env.example', '.env.sample', '.env.template', 'example.env'];
|
|
133
|
+
|
|
134
|
+
for (const name of envExampleNames) {
|
|
135
|
+
const filePath = path.join(projectPath, name);
|
|
136
|
+
if (fs.existsSync(filePath)) {
|
|
137
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
138
|
+
const vars = parseEnvFile(content);
|
|
139
|
+
return { file: name, vars };
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Check if .env exists
|
|
148
|
+
*/
|
|
149
|
+
export function hasEnvFile(projectPath) {
|
|
150
|
+
return fs.existsSync(path.join(projectPath, '.env'));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Parse env file content into variables
|
|
155
|
+
*/
|
|
156
|
+
export function parseEnvFile(content) {
|
|
157
|
+
const vars = [];
|
|
158
|
+
const lines = content.split('\n');
|
|
159
|
+
|
|
160
|
+
for (const line of lines) {
|
|
161
|
+
const trimmed = line.trim();
|
|
162
|
+
|
|
163
|
+
// Skip empty lines and comments
|
|
164
|
+
if (!trimmed || trimmed.startsWith('#')) {
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Parse KEY=value
|
|
169
|
+
const match = trimmed.match(/^([A-Z_][A-Z0-9_]*)=(.*)$/i);
|
|
170
|
+
if (match) {
|
|
171
|
+
const [, key, value] = match;
|
|
172
|
+
const cleanValue = value.replace(/^["']|["']$/g, ''); // Remove quotes
|
|
173
|
+
|
|
174
|
+
vars.push({
|
|
175
|
+
key,
|
|
176
|
+
defaultValue: cleanValue || null,
|
|
177
|
+
hasDefault: cleanValue !== '',
|
|
178
|
+
isSecret: isSecretVar(key)
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return vars;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Detect if variable is likely a secret
|
|
188
|
+
*/
|
|
189
|
+
function isSecretVar(key) {
|
|
190
|
+
const secretPatterns = [
|
|
191
|
+
/KEY$/i,
|
|
192
|
+
/SECRET$/i,
|
|
193
|
+
/TOKEN$/i,
|
|
194
|
+
/PASSWORD$/i,
|
|
195
|
+
/PASS$/i,
|
|
196
|
+
/AUTH/i,
|
|
197
|
+
/PRIVATE/i,
|
|
198
|
+
/CREDENTIAL/i
|
|
199
|
+
];
|
|
200
|
+
|
|
201
|
+
return secretPatterns.some(pattern => pattern.test(key));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Prompt user for env values
|
|
206
|
+
*/
|
|
207
|
+
export async function promptEnvValues(vars, existingEnv = {}) {
|
|
208
|
+
const values = { ...existingEnv };
|
|
209
|
+
const questions = [];
|
|
210
|
+
|
|
211
|
+
for (const v of vars) {
|
|
212
|
+
// Skip if already has value
|
|
213
|
+
if (existingEnv[v.key]) {
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const defaultVal = v.hasDefault ? v.defaultValue : undefined;
|
|
218
|
+
|
|
219
|
+
questions.push({
|
|
220
|
+
type: v.isSecret ? 'password' : 'input',
|
|
221
|
+
name: v.key,
|
|
222
|
+
message: v.isSecret
|
|
223
|
+
? `${v.key}: (secret)`
|
|
224
|
+
: `${v.key}:`,
|
|
225
|
+
default: defaultVal,
|
|
226
|
+
transformer: (input) => {
|
|
227
|
+
if (v.isSecret && input) {
|
|
228
|
+
return '*'.repeat(input.length);
|
|
229
|
+
}
|
|
230
|
+
return input;
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (questions.length === 0) {
|
|
236
|
+
return values;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
console.log(chalk.cyan('\nEnvironment variables needed:\n'));
|
|
240
|
+
|
|
241
|
+
const answers = await inquirer.prompt(questions);
|
|
242
|
+
|
|
243
|
+
return { ...values, ...answers };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Write .env file
|
|
248
|
+
*/
|
|
249
|
+
export function writeEnvFile(projectPath, values) {
|
|
250
|
+
const lines = [];
|
|
251
|
+
|
|
252
|
+
for (const [key, value] of Object.entries(values)) {
|
|
253
|
+
// Quote values with spaces or special chars
|
|
254
|
+
const needsQuotes = /[\s#"'`$]/.test(value);
|
|
255
|
+
const formattedValue = needsQuotes ? `"${value}"` : value;
|
|
256
|
+
lines.push(`${key}=${formattedValue}`);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const content = lines.join('\n') + '\n';
|
|
260
|
+
fs.writeFileSync(path.join(projectPath, '.env'), content);
|
|
261
|
+
|
|
262
|
+
return lines.length;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Read existing .env file
|
|
267
|
+
*/
|
|
268
|
+
export function readEnvFile(projectPath) {
|
|
269
|
+
const envPath = path.join(projectPath, '.env');
|
|
270
|
+
if (!fs.existsSync(envPath)) {
|
|
271
|
+
return {};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const content = fs.readFileSync(envPath, 'utf-8');
|
|
275
|
+
const vars = parseEnvFile(content);
|
|
276
|
+
|
|
277
|
+
const result = {};
|
|
278
|
+
for (const v of vars) {
|
|
279
|
+
result[v.key] = v.defaultValue || '';
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return result;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Get startup commands from package.json
|
|
287
|
+
*/
|
|
288
|
+
export function getStartupCommands(projectPath) {
|
|
289
|
+
const pkgPath = path.join(projectPath, 'package.json');
|
|
290
|
+
if (!fs.existsSync(pkgPath)) {
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const pkg = fs.readJsonSync(pkgPath);
|
|
295
|
+
const scripts = pkg.scripts || {};
|
|
296
|
+
|
|
297
|
+
const startupScripts = [];
|
|
298
|
+
const commonStartScripts = ['dev', 'start', 'serve', 'develop', 'start:dev'];
|
|
299
|
+
|
|
300
|
+
for (const name of commonStartScripts) {
|
|
301
|
+
if (scripts[name]) {
|
|
302
|
+
startupScripts.push({ name, command: scripts[name] });
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Also include build if exists
|
|
307
|
+
if (scripts.build) {
|
|
308
|
+
startupScripts.push({ name: 'build', command: scripts.build });
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return startupScripts.length > 0 ? startupScripts : null;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Run full project setup
|
|
316
|
+
*/
|
|
317
|
+
export async function runProjectSetup(projectPath, options = {}) {
|
|
318
|
+
const results = {
|
|
319
|
+
packageManager: null,
|
|
320
|
+
depsInstalled: false,
|
|
321
|
+
nodeVersion: null,
|
|
322
|
+
envCreated: false,
|
|
323
|
+
startupCommands: null
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
// 1. Detect package manager
|
|
327
|
+
const pm = detectPackageManager(projectPath);
|
|
328
|
+
if (!pm) {
|
|
329
|
+
return results; // Not a Node.js project
|
|
330
|
+
}
|
|
331
|
+
results.packageManager = pm;
|
|
332
|
+
|
|
333
|
+
// 2. Check node version
|
|
334
|
+
const nodeReq = detectNodeVersion(projectPath);
|
|
335
|
+
if (nodeReq) {
|
|
336
|
+
const currentNode = getCurrentNodeVersion();
|
|
337
|
+
const check = checkNodeVersion(nodeReq.version, currentNode);
|
|
338
|
+
results.nodeVersion = {
|
|
339
|
+
required: nodeReq.version,
|
|
340
|
+
current: currentNode,
|
|
341
|
+
file: nodeReq.file,
|
|
342
|
+
ok: check.ok,
|
|
343
|
+
warning: check.warning
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
if (!check.ok) {
|
|
347
|
+
console.log(chalk.yellow(`\n⚠️ Node version mismatch`));
|
|
348
|
+
console.log(chalk.yellow(` Required: ${nodeReq.version} (from ${nodeReq.file})`));
|
|
349
|
+
console.log(chalk.yellow(` Current: ${currentNode}`));
|
|
350
|
+
console.log(chalk.gray(` Consider using nvm: nvm install ${nodeReq.version}\n`));
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// 3. Install dependencies if needed
|
|
355
|
+
if (!hasNodeModules(projectPath)) {
|
|
356
|
+
if (!options.yes) {
|
|
357
|
+
const { install } = await inquirer.prompt([{
|
|
358
|
+
type: 'confirm',
|
|
359
|
+
name: 'install',
|
|
360
|
+
message: `No node_modules found. Run ${chalk.cyan(pm.install)}?`,
|
|
361
|
+
default: true
|
|
362
|
+
}]);
|
|
363
|
+
|
|
364
|
+
if (install) {
|
|
365
|
+
console.log(chalk.cyan(`\nRunning ${pm.install}...\n`));
|
|
366
|
+
results.depsInstalled = await installDeps(projectPath, pm);
|
|
367
|
+
}
|
|
368
|
+
} else {
|
|
369
|
+
console.log(chalk.cyan(`\nRunning ${pm.install}...\n`));
|
|
370
|
+
results.depsInstalled = await installDeps(projectPath, pm);
|
|
371
|
+
}
|
|
372
|
+
} else {
|
|
373
|
+
results.depsInstalled = true; // Already installed
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// 4. Setup .env from example
|
|
377
|
+
// Always prompt for env values since they're required to run the app
|
|
378
|
+
const envExample = detectEnvExample(projectPath);
|
|
379
|
+
if (envExample) {
|
|
380
|
+
const hasEnv = hasEnvFile(projectPath);
|
|
381
|
+
|
|
382
|
+
if (!hasEnv) {
|
|
383
|
+
console.log(chalk.cyan(`\nFound ${envExample.file} with ${envExample.vars.length} variables`));
|
|
384
|
+
|
|
385
|
+
// Check if there are any vars that need user input (no default or secrets)
|
|
386
|
+
const needsInput = envExample.vars.some(v => !v.hasDefault || v.isSecret);
|
|
387
|
+
|
|
388
|
+
if (needsInput) {
|
|
389
|
+
const { setupEnv } = await inquirer.prompt([{
|
|
390
|
+
type: 'confirm',
|
|
391
|
+
name: 'setupEnv',
|
|
392
|
+
message: 'Set up .env file now? (required to run the app)',
|
|
393
|
+
default: true
|
|
394
|
+
}]);
|
|
395
|
+
|
|
396
|
+
if (setupEnv) {
|
|
397
|
+
const values = await promptEnvValues(envExample.vars);
|
|
398
|
+
const count = writeEnvFile(projectPath, values);
|
|
399
|
+
console.log(chalk.green(`\n✓ Created .env with ${count} variables`));
|
|
400
|
+
results.envCreated = true;
|
|
401
|
+
}
|
|
402
|
+
} else {
|
|
403
|
+
// All vars have defaults, auto-create .env
|
|
404
|
+
const values = {};
|
|
405
|
+
for (const v of envExample.vars) {
|
|
406
|
+
values[v.key] = v.defaultValue || '';
|
|
407
|
+
}
|
|
408
|
+
const count = writeEnvFile(projectPath, values);
|
|
409
|
+
console.log(chalk.green(`✓ Created .env with ${count} variables (using defaults)`));
|
|
410
|
+
results.envCreated = true;
|
|
411
|
+
}
|
|
412
|
+
} else {
|
|
413
|
+
// Check for missing vars in existing .env
|
|
414
|
+
const existingVars = readEnvFile(projectPath);
|
|
415
|
+
const missingVars = envExample.vars.filter(v => !existingVars[v.key]);
|
|
416
|
+
|
|
417
|
+
if (missingVars.length > 0) {
|
|
418
|
+
console.log(chalk.yellow(`\n⚠️ .env exists but missing ${missingVars.length} variables from ${envExample.file}`));
|
|
419
|
+
|
|
420
|
+
const { addMissing } = await inquirer.prompt([{
|
|
421
|
+
type: 'confirm',
|
|
422
|
+
name: 'addMissing',
|
|
423
|
+
message: 'Add missing variables?',
|
|
424
|
+
default: true
|
|
425
|
+
}]);
|
|
426
|
+
|
|
427
|
+
if (addMissing) {
|
|
428
|
+
const newValues = await promptEnvValues(missingVars, existingVars);
|
|
429
|
+
const count = writeEnvFile(projectPath, newValues);
|
|
430
|
+
console.log(chalk.green(`\n✓ Updated .env with ${count} variables`));
|
|
431
|
+
results.envCreated = true;
|
|
432
|
+
}
|
|
433
|
+
} else {
|
|
434
|
+
console.log(chalk.green(`✓ .env already configured`));
|
|
435
|
+
results.envCreated = true;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// 5. Get startup commands
|
|
441
|
+
results.startupCommands = getStartupCommands(projectPath);
|
|
442
|
+
|
|
443
|
+
return results;
|
|
444
|
+
}
|
package/src/utils/skills.js
CHANGED
|
@@ -11,8 +11,10 @@ import { fileURLToPath } from 'url';
|
|
|
11
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
12
12
|
const __dirname = path.dirname(__filename);
|
|
13
13
|
|
|
14
|
-
//
|
|
14
|
+
// Paths to bundled content (in npm package)
|
|
15
15
|
const SKILLS_SOURCE = path.join(__dirname, '../../skills');
|
|
16
|
+
const KNOWLEDGE_SOURCE = path.join(__dirname, '../../knowledge');
|
|
17
|
+
const SCRIPTS_SOURCE = path.join(__dirname, '../../project-scripts');
|
|
16
18
|
|
|
17
19
|
/**
|
|
18
20
|
* Get list of all available skills
|
|
@@ -251,3 +253,87 @@ export async function checkForUpdates(projectPath) {
|
|
|
251
253
|
|
|
252
254
|
return updates;
|
|
253
255
|
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Copy knowledge (stacks, domains) to project
|
|
259
|
+
*/
|
|
260
|
+
export async function copyKnowledgeToProject(projectPath) {
|
|
261
|
+
const targetPath = path.join(projectPath, '.claude', 'knowledge');
|
|
262
|
+
|
|
263
|
+
// Check if source exists
|
|
264
|
+
if (!await fs.pathExists(KNOWLEDGE_SOURCE)) {
|
|
265
|
+
return { copied: [], errors: [{ name: 'knowledge', error: 'Source not found' }] };
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Remove existing and copy fresh
|
|
269
|
+
await fs.remove(targetPath);
|
|
270
|
+
await fs.ensureDir(targetPath);
|
|
271
|
+
|
|
272
|
+
const copied = [];
|
|
273
|
+
const errors = [];
|
|
274
|
+
|
|
275
|
+
try {
|
|
276
|
+
const items = await fs.readdir(KNOWLEDGE_SOURCE);
|
|
277
|
+
|
|
278
|
+
for (const item of items) {
|
|
279
|
+
// Skip hidden files
|
|
280
|
+
if (item.startsWith('.')) continue;
|
|
281
|
+
|
|
282
|
+
const sourcePath = path.join(KNOWLEDGE_SOURCE, item);
|
|
283
|
+
const targetItemPath = path.join(targetPath, item);
|
|
284
|
+
|
|
285
|
+
try {
|
|
286
|
+
await fs.copy(sourcePath, targetItemPath);
|
|
287
|
+
copied.push(item);
|
|
288
|
+
} catch (e) {
|
|
289
|
+
errors.push({ name: item, error: e.message });
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
} catch (e) {
|
|
293
|
+
errors.push({ name: 'knowledge', error: e.message });
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return { copied, errors };
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Copy scripts (graph.py, etc.) to project
|
|
301
|
+
*/
|
|
302
|
+
export async function copyScriptsToProject(projectPath) {
|
|
303
|
+
const targetPath = path.join(projectPath, '.claude', 'scripts');
|
|
304
|
+
|
|
305
|
+
// Check if source exists
|
|
306
|
+
if (!await fs.pathExists(SCRIPTS_SOURCE)) {
|
|
307
|
+
return { copied: [], errors: [{ name: 'scripts', error: 'Source not found' }] };
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Remove existing and copy fresh
|
|
311
|
+
await fs.remove(targetPath);
|
|
312
|
+
await fs.ensureDir(targetPath);
|
|
313
|
+
|
|
314
|
+
const copied = [];
|
|
315
|
+
const errors = [];
|
|
316
|
+
|
|
317
|
+
try {
|
|
318
|
+
const items = await fs.readdir(SCRIPTS_SOURCE);
|
|
319
|
+
|
|
320
|
+
for (const item of items) {
|
|
321
|
+
// Skip hidden files
|
|
322
|
+
if (item.startsWith('.')) continue;
|
|
323
|
+
|
|
324
|
+
const sourcePath = path.join(SCRIPTS_SOURCE, item);
|
|
325
|
+
const targetItemPath = path.join(targetPath, item);
|
|
326
|
+
|
|
327
|
+
try {
|
|
328
|
+
await fs.copy(sourcePath, targetItemPath);
|
|
329
|
+
copied.push(item);
|
|
330
|
+
} catch (e) {
|
|
331
|
+
errors.push({ name: item, error: e.message });
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
} catch (e) {
|
|
335
|
+
errors.push({ name: 'scripts', error: e.message });
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return { copied, errors };
|
|
339
|
+
}
|
|
File without changes
|