@slahon/lazykit 1.0.7 → 1.0.8

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/README.md +1 -1
  2. package/git.js +10 -1
  3. package/init.js +62 -24
  4. package/package.json +3 -3
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,9 +43,32 @@ 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
 
@@ -55,7 +78,7 @@ async function init() {
55
78
 
56
79
  log.blank();
57
80
 
58
- // ─── Step 3: Create workflow file ────────────────────────────────────────
81
+ // ─── Step 4: Create workflow file ────────────────────────────────────────
59
82
  const spinner = ora({ text: 'Creating workflow file...', color: 'cyan' }).start();
60
83
 
61
84
  const workflowDir = path.join(process.cwd(), '.github', 'workflows');
@@ -67,7 +90,7 @@ async function init() {
67
90
 
68
91
  spinner.succeed(chalk.green('Created .github/workflows/lazykit.yml'));
69
92
 
70
- // ─── Step 4: Create CLAUDE.md ────────────────────────────────────────────
93
+ // ─── Step 5: Create CLAUDE.md ────────────────────────────────────────────
71
94
  if (wantClaudeMd) {
72
95
  const claudeMdPath = path.join(process.cwd(), 'CLAUDE.md');
73
96
  const claudeMdContent = generateClaudeMd({ stack });
@@ -86,10 +109,8 @@ async function init() {
86
109
  }
87
110
  }
88
111
 
89
- // ─── Step 5: Create GitHub label ─────────────────────────────────────────
90
- const ghAvailable = isGhCliAvailable();
91
-
92
- if (ghAvailable && repoInfo) {
112
+ // ─── Step 6: Create GitHub label ─────────────────────────────────────────
113
+ if (ghReady) {
93
114
  const labelSpinner = ora({ text: `Creating '${label}' label on GitHub...`, color: 'cyan' }).start();
94
115
  try {
95
116
  execSync(
@@ -106,11 +127,11 @@ async function init() {
106
127
  }
107
128
  }
108
129
  } else {
109
- log.warn(`gh CLI not found — create the '${label}' label manually on GitHub`);
130
+ log.warn(`Create the '${label}' label manually at: ${chalk.cyan(`https://github.com/${repoInfo.owner}/${repoInfo.repo}/labels`)}`);
110
131
  }
111
132
 
112
- // ─── Step 6: Enable Actions to create PRs ────────────────────────────────
113
- if (ghAvailable && repoInfo) {
133
+ // ─── Step 7: Enable Actions to create PRs ────────────────────────────────
134
+ if (ghReady) {
114
135
  const prSpinner = ora({ text: 'Enabling Actions PR creation permission...', color: 'cyan' }).start();
115
136
  try {
116
137
  execSync(
@@ -118,12 +139,20 @@ async function init() {
118
139
  { stdio: 'pipe' }
119
140
  );
120
141
  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'));
142
+ } catch (err) {
143
+ const msg = err.stderr?.toString() || '';
144
+ if (msg.includes('403') || msg.includes('Must have admin rights')) {
145
+ prSpinner.warn(chalk.yellow('Permission denied — you need repo admin rights to change this setting.'));
146
+ console.log(chalk.gray(`\n Enable it manually: ${chalk.cyan(`https://github.com/${repoInfo.owner}/${repoInfo.repo}/settings/actions`)}`));
147
+ console.log(chalk.gray(' Actions → General → Workflow permissions → Enable "Allow GitHub Actions to create and approve pull requests"\n'));
148
+ } else {
149
+ prSpinner.warn(chalk.yellow('Could not enable PR creation automatically.'));
150
+ console.log(chalk.gray(` Enable manually: ${chalk.cyan(`https://github.com/${repoInfo.owner}/${repoInfo.repo}/settings/actions`)}\n`));
151
+ }
123
152
  }
124
153
  }
125
154
 
126
- // ─── Step 7: Generate and set Claude token ───────────────────────────────
155
+ // ─── Step 8: Generate and set Claude token ───────────────────────────────
127
156
  console.log('\n' + chalk.bold.green(' ✨ Almost there — setting up your Claude token...\n'));
128
157
 
129
158
  let tokenSet = false;
@@ -133,14 +162,16 @@ async function init() {
133
162
  console.log();
134
163
 
135
164
  const result = spawnSync('claude', ['setup-token'], {
136
- stdio: ['inherit', 'pipe', 'inherit'],
165
+ stdio: ['inherit', 'pipe', 'pipe'],
137
166
  encoding: 'utf8',
138
167
  });
139
168
 
140
- const output = result.stdout || '';
141
- const tokenMatch = output.match(/sk-ant-oat\d*-[\w-]+/);
169
+ if (result.stderr) process.stderr.write(result.stderr);
142
170
 
143
- if (tokenMatch && ghAvailable) {
171
+ const output = (result.stdout || '') + (result.stderr || '');
172
+ const tokenMatch = output.match(/sk-ant-oat[^\s]+/);
173
+
174
+ if (tokenMatch && ghReady) {
144
175
  const token = tokenMatch[0];
145
176
  const secretSpinner = ora({ text: 'Adding CLAUDE_CODE_OAUTH_TOKEN to GitHub secrets...', color: 'cyan' }).start();
146
177
  execSync(
@@ -149,7 +180,7 @@ async function init() {
149
180
  );
150
181
  secretSpinner.succeed(chalk.green('CLAUDE_CODE_OAUTH_TOKEN added to GitHub secrets'));
151
182
  tokenSet = true;
152
- } else if (tokenMatch && !ghAvailable) {
183
+ } else if (tokenMatch && !ghReady) {
153
184
  log.warn('Token generated but gh CLI not found — add it manually:');
154
185
  console.log(chalk.gray(' Name: ') + chalk.cyan('CLAUDE_CODE_OAUTH_TOKEN'));
155
186
  console.log(chalk.gray(' Value: ') + chalk.cyan(tokenMatch[0]));
@@ -157,11 +188,18 @@ async function init() {
157
188
  } else {
158
189
  throw new Error('Token not found in output');
159
190
  }
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'));
191
+ } catch (err) {
192
+ const isNotFound = err.status === 'ENOENT' || (err.message || '').includes('ENOENT');
193
+ if (isNotFound) {
194
+ log.warn('Claude Code CLI not found — install it first:');
195
+ console.log(chalk.cyan('\n npm install -g @anthropic-ai/claude-code'));
196
+ } else {
197
+ log.warn('Could not set token automatically.');
198
+ }
199
+ console.log(chalk.gray('\n Add it manually:'));
200
+ console.log(chalk.gray(' 1. Run: ') + chalk.cyan('claude setup-token'));
201
+ console.log(chalk.gray(' 2. Go to: ') + chalk.cyan(`https://github.com/${repoInfo.owner}/${repoInfo.repo}/settings/secrets/actions`));
202
+ console.log(chalk.gray(' 3. Add secret:') + chalk.cyan(' CLAUDE_CODE_OAUTH_TOKEN') + chalk.gray(' = the token you copied\n'));
165
203
  }
166
204
 
167
205
  log.divider();
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@slahon/lazykit",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
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",