@slahon/lazykit 1.0.7 → 1.1.1

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
@@ -7,7 +7,7 @@ LazyKit wires Claude AI into your GitHub repo so that when you label an issue, C
7
7
  ## Quickstart
8
8
 
9
9
  ```bash
10
- npx @slahon/lazykit init
10
+ npx @slahon/lazykit@latest init
11
11
  ```
12
12
 
13
13
  Run this inside your project folder. It will:
package/git.js CHANGED
@@ -45,6 +45,15 @@ function isGhCliAvailable() {
45
45
  }
46
46
  }
47
47
 
48
+ function isGhAuthenticated() {
49
+ try {
50
+ execSync('gh auth status', { stdio: 'pipe' });
51
+ return true;
52
+ } catch {
53
+ return false;
54
+ }
55
+ }
56
+
48
57
  function detectStack() {
49
58
  const cwd = process.cwd();
50
59
  const stack = [];
@@ -82,4 +91,4 @@ function detectStack() {
82
91
  return stack.length > 0 ? stack.join(' + ') : null;
83
92
  }
84
93
 
85
- module.exports = { getGitRemote, parseGitHubRepo, isGitRepo, isGhCliAvailable, detectStack };
94
+ module.exports = { getGitRemote, parseGitHubRepo, isGitRepo, isGhCliAvailable, isGhAuthenticated, detectStack };
package/init.js CHANGED
@@ -8,7 +8,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, detectStack } = require('./git');
11
+ const { isGitRepo, getGitRemote, parseGitHubRepo, isGhCliAvailable, isGhAuthenticated, detectStack } = require('./git');
12
12
  const { generateWorkflow } = require('./workflow');
13
13
  const { generateClaudeMd } = require('./claude-md');
14
14
 
@@ -43,31 +43,56 @@ async function init() {
43
43
  process.exit(1);
44
44
  }
45
45
 
46
+ // ─── Step 2: Check gh CLI and auth ──────────────────────────────────────
47
+ const ghAvailable = isGhCliAvailable();
48
+ let ghReady = false;
49
+
50
+ if (!ghAvailable) {
51
+ log.warn('GitHub CLI (gh) not found.');
52
+ console.log(chalk.gray('\n LazyKit uses gh to create labels, set secrets, and enable PR permissions.'));
53
+ console.log(chalk.gray(' Without it, those steps will be skipped and you will need to do them manually.\n'));
54
+ console.log(chalk.gray(' Install gh from: ') + chalk.cyan('https://cli.github.com'));
55
+ console.log(chalk.gray(' Then re-run: ') + chalk.cyan('npx @slahon/lazykit@latest init\n'));
56
+ const cont = await confirm('Continue without gh? (some steps will be manual)', false);
57
+ if (!cont) process.exit(0);
58
+ } else if (!isGhAuthenticated()) {
59
+ log.warn('GitHub CLI is not authenticated.');
60
+ console.log(chalk.gray('\n Run the following to log in, then re-run LazyKit:\n'));
61
+ console.log(chalk.cyan(' gh auth login'));
62
+ console.log(chalk.gray('\n Then re-run: ') + chalk.cyan('npx @slahon/lazykit@latest init\n'));
63
+ const cont = await confirm('Continue without gh auth? (some steps will be manual)', false);
64
+ if (!cont) process.exit(0);
65
+ } else {
66
+ ghReady = true;
67
+ }
68
+
46
69
  log.blank();
47
70
 
48
- // ─── Step 2: Ask questions ───────────────────────────────────────────────
71
+ // ─── Step 3: Ask questions ───────────────────────────────────────────────
49
72
  log.title('Configure LazyKit');
50
73
  log.blank();
51
74
 
52
75
  const label = await ask('Label name to trigger Claude', 'lazykit');
53
76
 
77
+ const autoTrigger = await confirm('Trigger Claude on every new issue automatically? (no = only when you apply the label)', true);
78
+
54
79
  const wantClaudeMd = await confirm('Generate CLAUDE.md project guide?', true);
55
80
 
56
81
  log.blank();
57
82
 
58
- // ─── Step 3: Create workflow file ────────────────────────────────────────
83
+ // ─── Step 4: Create workflow file ────────────────────────────────────────
59
84
  const spinner = ora({ text: 'Creating workflow file...', color: 'cyan' }).start();
60
85
 
61
86
  const workflowDir = path.join(process.cwd(), '.github', 'workflows');
62
87
  fs.mkdirSync(workflowDir, { recursive: true });
63
88
 
64
89
  const workflowPath = path.join(workflowDir, 'lazykit.yml');
65
- const workflowContent = generateWorkflow({ label });
90
+ const workflowContent = generateWorkflow({ label, autoTrigger });
66
91
  fs.writeFileSync(workflowPath, workflowContent);
67
92
 
68
93
  spinner.succeed(chalk.green('Created .github/workflows/lazykit.yml'));
69
94
 
70
- // ─── Step 4: Create CLAUDE.md ────────────────────────────────────────────
95
+ // ─── Step 5: Create CLAUDE.md ────────────────────────────────────────────
71
96
  if (wantClaudeMd) {
72
97
  const claudeMdPath = path.join(process.cwd(), 'CLAUDE.md');
73
98
  const claudeMdContent = generateClaudeMd({ stack });
@@ -86,10 +111,8 @@ async function init() {
86
111
  }
87
112
  }
88
113
 
89
- // ─── Step 5: Create GitHub label ─────────────────────────────────────────
90
- const ghAvailable = isGhCliAvailable();
91
-
92
- if (ghAvailable && repoInfo) {
114
+ // ─── Step 6: Create GitHub label ─────────────────────────────────────────
115
+ if (ghReady) {
93
116
  const labelSpinner = ora({ text: `Creating '${label}' label on GitHub...`, color: 'cyan' }).start();
94
117
  try {
95
118
  execSync(
@@ -106,11 +129,11 @@ async function init() {
106
129
  }
107
130
  }
108
131
  } else {
109
- log.warn(`gh CLI not found — create the '${label}' label manually on GitHub`);
132
+ log.warn(`Create the '${label}' label manually at: ${chalk.cyan(`https://github.com/${repoInfo.owner}/${repoInfo.repo}/labels`)}`);
110
133
  }
111
134
 
112
- // ─── Step 6: Enable Actions to create PRs ────────────────────────────────
113
- if (ghAvailable && repoInfo) {
135
+ // ─── Step 7: Enable Actions to create PRs ────────────────────────────────
136
+ if (ghReady) {
114
137
  const prSpinner = ora({ text: 'Enabling Actions PR creation permission...', color: 'cyan' }).start();
115
138
  try {
116
139
  execSync(
@@ -118,12 +141,20 @@ async function init() {
118
141
  { stdio: 'pipe' }
119
142
  );
120
143
  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'));
144
+ } catch (err) {
145
+ const msg = err.stderr?.toString() || '';
146
+ if (msg.includes('403') || msg.includes('Must have admin rights')) {
147
+ prSpinner.warn(chalk.yellow('Permission denied — you need repo admin rights to change this setting.'));
148
+ console.log(chalk.gray(`\n Enable it manually: ${chalk.cyan(`https://github.com/${repoInfo.owner}/${repoInfo.repo}/settings/actions`)}`));
149
+ console.log(chalk.gray(' Actions → General → Workflow permissions → Enable "Allow GitHub Actions to create and approve pull requests"\n'));
150
+ } else {
151
+ prSpinner.warn(chalk.yellow('Could not enable PR creation automatically.'));
152
+ console.log(chalk.gray(` Enable manually: ${chalk.cyan(`https://github.com/${repoInfo.owner}/${repoInfo.repo}/settings/actions`)}\n`));
153
+ }
123
154
  }
124
155
  }
125
156
 
126
- // ─── Step 7: Generate and set Claude token ───────────────────────────────
157
+ // ─── Step 8: Generate and set Claude token ───────────────────────────────
127
158
  console.log('\n' + chalk.bold.green(' ✨ Almost there — setting up your Claude token...\n'));
128
159
 
129
160
  let tokenSet = false;
@@ -133,14 +164,16 @@ async function init() {
133
164
  console.log();
134
165
 
135
166
  const result = spawnSync('claude', ['setup-token'], {
136
- stdio: ['inherit', 'pipe', 'inherit'],
167
+ stdio: ['inherit', 'pipe', 'pipe'],
137
168
  encoding: 'utf8',
138
169
  });
139
170
 
140
- const output = result.stdout || '';
141
- const tokenMatch = output.match(/sk-ant-oat\d*-[\w-]+/);
171
+ if (result.stderr) process.stderr.write(result.stderr);
172
+
173
+ const output = (result.stdout || '') + (result.stderr || '');
174
+ const tokenMatch = output.match(/sk-ant-oat[^\s]+/);
142
175
 
143
- if (tokenMatch && ghAvailable) {
176
+ if (tokenMatch && ghReady) {
144
177
  const token = tokenMatch[0];
145
178
  const secretSpinner = ora({ text: 'Adding CLAUDE_CODE_OAUTH_TOKEN to GitHub secrets...', color: 'cyan' }).start();
146
179
  execSync(
@@ -149,7 +182,7 @@ async function init() {
149
182
  );
150
183
  secretSpinner.succeed(chalk.green('CLAUDE_CODE_OAUTH_TOKEN added to GitHub secrets'));
151
184
  tokenSet = true;
152
- } else if (tokenMatch && !ghAvailable) {
185
+ } else if (tokenMatch && !ghReady) {
153
186
  log.warn('Token generated but gh CLI not found — add it manually:');
154
187
  console.log(chalk.gray(' Name: ') + chalk.cyan('CLAUDE_CODE_OAUTH_TOKEN'));
155
188
  console.log(chalk.gray(' Value: ') + chalk.cyan(tokenMatch[0]));
@@ -157,19 +190,46 @@ async function init() {
157
190
  } else {
158
191
  throw new Error('Token not found in output');
159
192
  }
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'));
193
+ } catch (err) {
194
+ const isNotFound = err.status === 'ENOENT' || (err.message || '').includes('ENOENT');
195
+ if (isNotFound) {
196
+ log.warn('Claude Code CLI not found — install it first:');
197
+ console.log(chalk.cyan('\n npm install -g @anthropic-ai/claude-code'));
198
+ } else {
199
+ log.warn('Could not set token automatically.');
200
+ }
201
+ console.log(chalk.gray('\n Add it manually:'));
202
+ console.log(chalk.gray(' 1. Run: ') + chalk.cyan('claude setup-token'));
203
+ console.log(chalk.gray(' 2. Go to: ') + chalk.cyan(`https://github.com/${repoInfo.owner}/${repoInfo.repo}/settings/secrets/actions`));
204
+ console.log(chalk.gray(' 3. Add secret:') + chalk.cyan(' CLAUDE_CODE_OAUTH_TOKEN') + chalk.gray(' = the token you copied\n'));
165
205
  }
166
206
 
167
207
  log.divider();
168
208
 
169
- console.log(chalk.bold('\n Then commit and push the new files:\n'));
170
- console.log(chalk.cyan(' git add .github/workflows/lazykit.yml' + (wantClaudeMd ? ' CLAUDE.md' : '')));
171
- console.log(chalk.cyan(' git commit -m "Add LazyKit automation"'));
172
- console.log(chalk.cyan(' git push'));
209
+ // ─── Step 9: Commit and push ──────────────────────────────────────────────
210
+ const autoPush = await confirm('Commit and push the generated files now?', true);
211
+
212
+ if (autoPush) {
213
+ const pushSpinner = ora({ text: 'Committing and pushing...', color: 'cyan' }).start();
214
+ try {
215
+ const files = ['.github/workflows/lazykit.yml', ...(wantClaudeMd ? ['CLAUDE.md'] : [])].join(' ');
216
+ execSync(`git add ${files}`, { stdio: 'pipe' });
217
+ execSync(`git commit -m "Add LazyKit automation"`, { stdio: 'pipe' });
218
+ execSync(`git push`, { stdio: 'pipe' });
219
+ pushSpinner.succeed(chalk.green('Committed and pushed — workflow is live'));
220
+ } catch (err) {
221
+ pushSpinner.fail(chalk.red('Push failed.'));
222
+ console.log(chalk.gray('\n Push it manually:\n'));
223
+ console.log(chalk.cyan(' git add .github/workflows/lazykit.yml' + (wantClaudeMd ? ' CLAUDE.md' : '')));
224
+ console.log(chalk.cyan(' git commit -m "Add LazyKit automation"'));
225
+ console.log(chalk.cyan(' git push\n'));
226
+ }
227
+ } else {
228
+ console.log(chalk.bold('\n Push the files yourself when ready:\n'));
229
+ console.log(chalk.cyan(' git add .github/workflows/lazykit.yml' + (wantClaudeMd ? ' CLAUDE.md' : '')));
230
+ console.log(chalk.cyan(' git commit -m "Add LazyKit automation"'));
231
+ console.log(chalk.cyan(' git push'));
232
+ }
173
233
 
174
234
  log.divider();
175
235
 
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@slahon/lazykit",
3
- "version": "1.0.7",
3
+ "version": "1.1.1",
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"
7
7
  },
8
8
  "scripts": {
9
- "start": "node bin/index.js"
9
+ "start": "node index.js"
10
10
  },
11
11
  "keywords": [
12
12
  "claude",
@@ -16,7 +16,7 @@
16
16
  "cli",
17
17
  "issue-to-pr"
18
18
  ],
19
- "author": "",
19
+ "author": "slahon",
20
20
  "license": "MIT",
21
21
  "dependencies": {
22
22
  "chalk": "^4.1.2",
package/workflow.js CHANGED
@@ -1,16 +1,30 @@
1
1
  'use strict';
2
2
 
3
- function generateWorkflow({ label }) {
3
+ function generateWorkflow({ label, autoTrigger }) {
4
4
  const checkList = ` 1. Review your changes and make sure no existing functionality is broken.
5
5
  2. If you encounter errors you cannot fix, post a comment on the issue explaining what went wrong instead of committing broken code.`;
6
6
 
7
- return `name: LazyKit
8
-
9
- on:
7
+ const trigger = autoTrigger
8
+ ? `on:
9
+ issues:
10
+ types: [opened]
11
+ issue_comment:
12
+ types: [created]`
13
+ : `on:
10
14
  issues:
11
15
  types: [opened, labeled]
12
16
  issue_comment:
13
- types: [created]
17
+ types: [created]`;
18
+
19
+ const condition = autoTrigger
20
+ ? `github.event.action == 'opened' ||
21
+ contains(github.event.comment.body, '@claude')`
22
+ : `contains(github.event.issue.labels.*.name, '${label}') ||
23
+ contains(github.event.comment.body, '@claude')`;
24
+
25
+ return `name: LazyKit
26
+
27
+ ${trigger}
14
28
 
15
29
  concurrency:
16
30
  group: lazykit-\${{ github.event.issue.number }}
@@ -19,8 +33,7 @@ concurrency:
19
33
  jobs:
20
34
  lazykit:
21
35
  if: >
22
- contains(github.event.issue.labels.*.name, '${label}') ||
23
- contains(github.event.comment.body, '@claude')
36
+ ${condition}
24
37
  runs-on: ubuntu-latest
25
38
  timeout-minutes: 30
26
39
  permissions: