@slahon/lazykit 1.0.3 → 1.0.7
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/claude-md.js +5 -8
- package/git.js +38 -1
- package/init.js +54 -33
- package/package.json +1 -1
- package/workflow.js +3 -11
package/claude-md.js
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
function generateClaudeMd({ stack
|
|
4
|
-
const hasLint = lintCommand && lintCommand !== 'skip';
|
|
5
|
-
const hasTest = testCommand && testCommand !== 'skip';
|
|
6
|
-
|
|
3
|
+
function generateClaudeMd({ stack }) {
|
|
7
4
|
return `# LazyKit — Project Guide for Claude
|
|
8
5
|
|
|
9
6
|
## Stack
|
|
10
|
-
${stack || '
|
|
7
|
+
${stack || 'Update this section with your tech stack (e.g. TypeScript + Next.js, Postgres via Prisma)'}
|
|
11
8
|
|
|
12
9
|
## Conventions
|
|
13
10
|
- Match the existing code style and patterns in whatever file you are editing.
|
|
@@ -15,9 +12,9 @@ ${stack || 'Describe your tech stack here (e.g. TypeScript + Next.js, Postgres v
|
|
|
15
12
|
- Keep changes scoped to what the issue asks for — do not refactor unrelated code.
|
|
16
13
|
|
|
17
14
|
## Before opening a PR, always:
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
3. If you
|
|
15
|
+
1. Check your code for obvious errors.
|
|
16
|
+
2. Make sure existing functionality is not broken.
|
|
17
|
+
3. If you encounter errors you cannot fix, comment on the issue explaining why instead of pushing broken code.
|
|
21
18
|
|
|
22
19
|
## Never
|
|
23
20
|
- Touch \`.github/workflows/\`, secrets, or CI configuration.
|
package/git.js
CHANGED
|
@@ -45,4 +45,41 @@ function isGhCliAvailable() {
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
function detectStack() {
|
|
49
|
+
const cwd = process.cwd();
|
|
50
|
+
const stack = [];
|
|
51
|
+
|
|
52
|
+
const pkgPath = require('path').join(cwd, 'package.json');
|
|
53
|
+
if (require('fs').existsSync(pkgPath)) {
|
|
54
|
+
try {
|
|
55
|
+
const pkg = JSON.parse(require('fs').readFileSync(pkgPath, 'utf8'));
|
|
56
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
57
|
+
|
|
58
|
+
if (deps['next']) stack.push('Next.js');
|
|
59
|
+
else if (deps['react']) stack.push('React');
|
|
60
|
+
else if (deps['vue']) stack.push('Vue');
|
|
61
|
+
else if (deps['@angular/core']) stack.push('Angular');
|
|
62
|
+
else if (deps['svelte']) stack.push('Svelte');
|
|
63
|
+
else if (deps['express']) stack.push('Express');
|
|
64
|
+
else if (deps['fastify']) stack.push('Fastify');
|
|
65
|
+
|
|
66
|
+
if (deps['typescript'] || require('fs').existsSync(require('path').join(cwd, 'tsconfig.json'))) stack.push('TypeScript');
|
|
67
|
+
|
|
68
|
+
if (deps['@prisma/client'] || deps['prisma']) stack.push('Prisma');
|
|
69
|
+
else if (deps['mongoose']) stack.push('MongoDB');
|
|
70
|
+
else if (deps['pg'] || deps['postgres']) stack.push('PostgreSQL');
|
|
71
|
+
else if (deps['mysql2']) stack.push('MySQL');
|
|
72
|
+
} catch {}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (require('fs').existsSync(require('path').join(cwd, 'requirements.txt')) ||
|
|
76
|
+
require('fs').existsSync(require('path').join(cwd, 'pyproject.toml'))) stack.push('Python');
|
|
77
|
+
if (require('fs').existsSync(require('path').join(cwd, 'go.mod'))) stack.push('Go');
|
|
78
|
+
if (require('fs').existsSync(require('path').join(cwd, 'Cargo.toml'))) stack.push('Rust');
|
|
79
|
+
if (require('fs').existsSync(require('path').join(cwd, 'pom.xml')) ||
|
|
80
|
+
require('fs').existsSync(require('path').join(cwd, 'build.gradle'))) stack.push('Java');
|
|
81
|
+
|
|
82
|
+
return stack.length > 0 ? stack.join(' + ') : null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = { getGitRemote, parseGitHubRepo, isGitRepo, isGhCliAvailable, detectStack };
|
package/init.js
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
|
-
const { execSync } = require('child_process');
|
|
5
|
+
const { execSync, spawnSync } = require('child_process');
|
|
6
6
|
const chalk = require('chalk');
|
|
7
7
|
const ora = require('ora');
|
|
8
8
|
|
|
9
9
|
const { log } = require('./logger');
|
|
10
10
|
const { ask, confirm, select } = require('./prompt');
|
|
11
|
-
const { isGitRepo, getGitRemote, parseGitHubRepo, isGhCliAvailable } = require('./git');
|
|
11
|
+
const { isGitRepo, getGitRemote, parseGitHubRepo, isGhCliAvailable, detectStack } = require('./git');
|
|
12
12
|
const { generateWorkflow } = require('./workflow');
|
|
13
13
|
const { generateClaudeMd } = require('./claude-md');
|
|
14
14
|
|
|
@@ -29,8 +29,11 @@ async function init() {
|
|
|
29
29
|
const remote = getGitRemote();
|
|
30
30
|
const repoInfo = parseGitHubRepo(remote);
|
|
31
31
|
|
|
32
|
+
const stack = detectStack();
|
|
33
|
+
|
|
32
34
|
if (repoInfo) {
|
|
33
35
|
log.success(`Detected repo: ${chalk.cyan(`${repoInfo.owner}/${repoInfo.repo}`)}`);
|
|
36
|
+
if (stack) log.success(`Detected stack: ${chalk.cyan(stack)}`);
|
|
34
37
|
} else {
|
|
35
38
|
log.error('No GitHub remote detected.');
|
|
36
39
|
console.log(chalk.gray('\n LazyKit requires a GitHub repository with a remote set up.\n'));
|
|
@@ -48,12 +51,6 @@ async function init() {
|
|
|
48
51
|
|
|
49
52
|
const label = await ask('Label name to trigger Claude', 'lazykit');
|
|
50
53
|
|
|
51
|
-
const stack = await ask('What is your stack?', 'e.g. TypeScript + Next.js, Postgres');
|
|
52
|
-
|
|
53
|
-
const lintCommand = await ask('Lint command', 'npm run lint');
|
|
54
|
-
|
|
55
|
-
const testCommand = await ask('Test command', 'npm test');
|
|
56
|
-
|
|
57
54
|
const wantClaudeMd = await confirm('Generate CLAUDE.md project guide?', true);
|
|
58
55
|
|
|
59
56
|
log.blank();
|
|
@@ -65,7 +62,7 @@ async function init() {
|
|
|
65
62
|
fs.mkdirSync(workflowDir, { recursive: true });
|
|
66
63
|
|
|
67
64
|
const workflowPath = path.join(workflowDir, 'lazykit.yml');
|
|
68
|
-
const workflowContent = generateWorkflow({ label
|
|
65
|
+
const workflowContent = generateWorkflow({ label });
|
|
69
66
|
fs.writeFileSync(workflowPath, workflowContent);
|
|
70
67
|
|
|
71
68
|
spinner.succeed(chalk.green('Created .github/workflows/lazykit.yml'));
|
|
@@ -73,7 +70,7 @@ async function init() {
|
|
|
73
70
|
// ─── Step 4: Create CLAUDE.md ────────────────────────────────────────────
|
|
74
71
|
if (wantClaudeMd) {
|
|
75
72
|
const claudeMdPath = path.join(process.cwd(), 'CLAUDE.md');
|
|
76
|
-
const claudeMdContent = generateClaudeMd({ stack
|
|
73
|
+
const claudeMdContent = generateClaudeMd({ stack });
|
|
77
74
|
|
|
78
75
|
if (fs.existsSync(claudeMdPath)) {
|
|
79
76
|
const overwrite = await confirm('CLAUDE.md already exists. Overwrite?', false);
|
|
@@ -112,36 +109,60 @@ async function init() {
|
|
|
112
109
|
log.warn(`gh CLI not found — create the '${label}' label manually on GitHub`);
|
|
113
110
|
}
|
|
114
111
|
|
|
115
|
-
// ─── Step 6:
|
|
116
|
-
|
|
117
|
-
|
|
112
|
+
// ─── Step 6: Enable Actions to create PRs ────────────────────────────────
|
|
113
|
+
if (ghAvailable && repoInfo) {
|
|
114
|
+
const prSpinner = ora({ text: 'Enabling Actions PR creation permission...', color: 'cyan' }).start();
|
|
115
|
+
try {
|
|
116
|
+
execSync(
|
|
117
|
+
`gh api repos/${repoInfo.owner}/${repoInfo.repo}/actions/permissions/workflow --method PUT --field default_workflow_permissions=write --field can_approve_pull_request_reviews=true`,
|
|
118
|
+
{ stdio: 'pipe' }
|
|
119
|
+
);
|
|
120
|
+
prSpinner.succeed(chalk.green('Enabled: Actions can create and approve pull requests'));
|
|
121
|
+
} catch {
|
|
122
|
+
prSpinner.warn(chalk.yellow('Could not enable PR creation automatically — enable it manually in repo Settings → Actions → General'));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
118
125
|
|
|
119
|
-
|
|
126
|
+
// ─── Step 7: Generate and set Claude token ───────────────────────────────
|
|
127
|
+
console.log('\n' + chalk.bold.green(' ✨ Almost there — setting up your Claude token...\n'));
|
|
120
128
|
|
|
121
|
-
|
|
122
|
-
console.log(chalk.cyan(' claude setup-token'));
|
|
129
|
+
let tokenSet = false;
|
|
123
130
|
|
|
124
|
-
|
|
131
|
+
try {
|
|
132
|
+
log.info('Running claude setup-token (a browser window may open to authenticate)...');
|
|
133
|
+
console.log();
|
|
125
134
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
console.log(chalk.gray('\n 3.') + ' Go to:');
|
|
131
|
-
console.log(chalk.cyan(' Your repo → Settings → Secrets and variables → Actions'));
|
|
132
|
-
}
|
|
135
|
+
const result = spawnSync('claude', ['setup-token'], {
|
|
136
|
+
stdio: ['inherit', 'pipe', 'inherit'],
|
|
137
|
+
encoding: 'utf8',
|
|
138
|
+
});
|
|
133
139
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
console.log(chalk.gray(' Value: ') + chalk.cyan('the token you copied'));
|
|
140
|
+
const output = result.stdout || '';
|
|
141
|
+
const tokenMatch = output.match(/sk-ant-oat\d*-[\w-]+/);
|
|
137
142
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
+
if (tokenMatch && ghAvailable) {
|
|
144
|
+
const token = tokenMatch[0];
|
|
145
|
+
const secretSpinner = ora({ text: 'Adding CLAUDE_CODE_OAUTH_TOKEN to GitHub secrets...', color: 'cyan' }).start();
|
|
146
|
+
execSync(
|
|
147
|
+
`gh secret set CLAUDE_CODE_OAUTH_TOKEN --body "${token}" --repo ${repoInfo.owner}/${repoInfo.repo}`,
|
|
148
|
+
{ stdio: 'pipe' }
|
|
149
|
+
);
|
|
150
|
+
secretSpinner.succeed(chalk.green('CLAUDE_CODE_OAUTH_TOKEN added to GitHub secrets'));
|
|
151
|
+
tokenSet = true;
|
|
152
|
+
} else if (tokenMatch && !ghAvailable) {
|
|
153
|
+
log.warn('Token generated but gh CLI not found — add it manually:');
|
|
154
|
+
console.log(chalk.gray(' Name: ') + chalk.cyan('CLAUDE_CODE_OAUTH_TOKEN'));
|
|
155
|
+
console.log(chalk.gray(' Value: ') + chalk.cyan(tokenMatch[0]));
|
|
156
|
+
console.log(chalk.cyan(` https://github.com/${repoInfo.owner}/${repoInfo.repo}/settings/secrets/actions`));
|
|
157
|
+
} else {
|
|
158
|
+
throw new Error('Token not found in output');
|
|
159
|
+
}
|
|
160
|
+
} catch {
|
|
161
|
+
log.warn('Could not set token automatically. Add it manually:');
|
|
162
|
+
console.log(chalk.gray('\n 1. Run: ') + chalk.cyan('claude setup-token'));
|
|
163
|
+
console.log(chalk.gray(' 2. Go to: ') + chalk.cyan(`https://github.com/${repoInfo.owner}/${repoInfo.repo}/settings/secrets/actions`));
|
|
164
|
+
console.log(chalk.gray(' 3. Add secret: ') + chalk.cyan('CLAUDE_CODE_OAUTH_TOKEN'));
|
|
143
165
|
}
|
|
144
|
-
console.log(chalk.gray(' Enable: ') + '"Allow GitHub Actions to create and approve pull requests"');
|
|
145
166
|
|
|
146
167
|
log.divider();
|
|
147
168
|
|
package/package.json
CHANGED
package/workflow.js
CHANGED
|
@@ -1,16 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
function generateWorkflow({ label
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const checks = [
|
|
8
|
-
hasLint ? `Run \`${lintCommand}\` and fix any issues before committing.` : null,
|
|
9
|
-
hasTest ? `Run \`${testCommand}\` and make sure all tests pass before committing.` : null,
|
|
10
|
-
'If lint or tests fail and you cannot fix them, do NOT commit — instead post a comment on the issue explaining what went wrong.',
|
|
11
|
-
].filter(Boolean);
|
|
12
|
-
|
|
13
|
-
const checkList = checks.map((c, i) => ` ${i + 1}. ${c}`).join('\n');
|
|
3
|
+
function generateWorkflow({ label }) {
|
|
4
|
+
const checkList = ` 1. Review your changes and make sure no existing functionality is broken.
|
|
5
|
+
2. If you encounter errors you cannot fix, post a comment on the issue explaining what went wrong instead of committing broken code.`;
|
|
14
6
|
|
|
15
7
|
return `name: LazyKit
|
|
16
8
|
|