@slahon/lazykit 1.0.5 → 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.
Files changed (4) hide show
  1. package/claude-md.js +1 -1
  2. package/git.js +38 -1
  3. package/init.js +52 -27
  4. package/package.json +1 -1
package/claude-md.js CHANGED
@@ -4,7 +4,7 @@ function generateClaudeMd({ stack }) {
4
4
  return `# LazyKit — Project Guide for Claude
5
5
 
6
6
  ## Stack
7
- ${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)'}
8
8
 
9
9
  ## Conventions
10
10
  - Match the existing code style and patterns in whatever file you are editing.
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,8 +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
54
  const wantClaudeMd = await confirm('Generate CLAUDE.md project guide?', true);
54
55
 
55
56
  log.blank();
@@ -108,36 +109,60 @@ async function init() {
108
109
  log.warn(`gh CLI not found — create the '${label}' label manually on GitHub`);
109
110
  }
110
111
 
111
- // ─── Step 6: Done! Print final instructions ───────────────────────────────
112
- console.log('\n' + chalk.bold.green(' ✨ LazyKit is almost ready!\n'));
113
- 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
+ }
114
125
 
115
- 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'));
116
128
 
117
- console.log(chalk.gray(' 1.') + ' Run in your terminal:');
118
- console.log(chalk.cyan(' claude setup-token'));
129
+ let tokenSet = false;
119
130
 
120
- 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();
121
134
 
122
- if (repoInfo) {
123
- console.log(chalk.gray('\n 3.') + ' Go to:');
124
- console.log(chalk.cyan(` https://github.com/${repoInfo.owner}/${repoInfo.repo}/settings/secrets/actions`));
125
- } else {
126
- console.log(chalk.gray('\n 3.') + ' Go to:');
127
- console.log(chalk.cyan(' Your repo → Settings → Secrets and variables → Actions'));
128
- }
135
+ const result = spawnSync('claude', ['setup-token'], {
136
+ stdio: ['inherit', 'pipe', 'inherit'],
137
+ encoding: 'utf8',
138
+ });
129
139
 
130
- console.log(chalk.gray('\n 4.') + ' Click ' + chalk.bold('"New repository secret"') + ' and add:');
131
- console.log(chalk.gray(' Name: ') + chalk.cyan('CLAUDE_CODE_OAUTH_TOKEN'));
132
- 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-]+/);
133
142
 
134
- console.log(chalk.gray('\n 5.') + ' Also enable PR creation:');
135
- if (repoInfo) {
136
- console.log(chalk.cyan(` https://github.com/${repoInfo.owner}/${repoInfo.repo}/settings/actions`));
137
- } else {
138
- 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'));
139
165
  }
140
- console.log(chalk.gray(' Enable: ') + '"Allow GitHub Actions to create and approve pull requests"');
141
166
 
142
167
  log.divider();
143
168
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slahon/lazykit",
3
- "version": "1.0.5",
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"