@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 CHANGED
@@ -1,13 +1,10 @@
1
1
  'use strict';
2
2
 
3
- function generateClaudeMd({ stack, lintCommand, testCommand }) {
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 || 'Describe your tech stack here (e.g. TypeScript + Next.js, Postgres via Prisma)'}
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
- ${hasLint ? `1. Run \`${lintCommand}\` and fix all errors and warnings.` : '1. Check your code for obvious errors.'}
19
- ${hasTest ? `2. Run \`${testCommand}\` and make sure all tests pass.` : '2. Make sure existing functionality is not broken.'}
20
- 3. If you cannot get lint/tests passing, comment on the issue explaining why instead of pushing broken code.
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
- module.exports = { getGitRemote, parseGitHubRepo, isGitRepo, isGhCliAvailable };
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, lintCommand, testCommand });
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, lintCommand, testCommand });
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: Done! Print final instructions ───────────────────────────────
116
- console.log('\n' + chalk.bold.green(' ✨ LazyKit is almost ready!\n'));
117
- log.divider();
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
- console.log(chalk.bold('\n One manual step add your Claude token as a repo secret:\n'));
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
- console.log(chalk.gray(' 1.') + ' Run in your terminal:');
122
- console.log(chalk.cyan(' claude setup-token'));
129
+ let tokenSet = false;
123
130
 
124
- console.log(chalk.gray('\n 2.') + ' Copy the token it prints ' + chalk.gray('(sk-ant-oat01-...)'));
131
+ try {
132
+ log.info('Running claude setup-token (a browser window may open to authenticate)...');
133
+ console.log();
125
134
 
126
- if (repoInfo) {
127
- console.log(chalk.gray('\n 3.') + ' Go to:');
128
- console.log(chalk.cyan(` https://github.com/${repoInfo.owner}/${repoInfo.repo}/settings/secrets/actions`));
129
- } else {
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
- console.log(chalk.gray('\n 4.') + ' Click ' + chalk.bold('"New repository secret"') + ' and add:');
135
- console.log(chalk.gray(' Name: ') + chalk.cyan('CLAUDE_CODE_OAUTH_TOKEN'));
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
- console.log(chalk.gray('\n 5.') + ' Also enable PR creation:');
139
- if (repoInfo) {
140
- console.log(chalk.cyan(` https://github.com/${repoInfo.owner}/${repoInfo.repo}/settings/actions`));
141
- } else {
142
- console.log(chalk.cyan(' Repo Settings Actions General → Workflow permissions'));
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slahon/lazykit",
3
- "version": "1.0.3",
3
+ "version": "1.0.7",
4
4
  "description": "Drop an issue, get a PR. AI-powered issue-to-PR automation using Claude.",
5
5
  "bin": {
6
6
  "lazykit": "./index.js"
package/workflow.js CHANGED
@@ -1,16 +1,8 @@
1
1
  'use strict';
2
2
 
3
- function generateWorkflow({ label, lintCommand, testCommand }) {
4
- const hasLint = lintCommand && lintCommand !== 'skip';
5
- const hasTest = testCommand && testCommand !== 'skip';
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