@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.
- package/README.md +1 -1
- package/git.js +10 -1
- package/init.js +62 -24
- package/package.json +3 -3
package/README.md
CHANGED
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
|
|
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
|
|
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
|
|
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
|
|
90
|
-
|
|
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(`
|
|
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
|
|
113
|
-
if (
|
|
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
|
-
|
|
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
|
|
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', '
|
|
165
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
137
166
|
encoding: 'utf8',
|
|
138
167
|
});
|
|
139
168
|
|
|
140
|
-
|
|
141
|
-
const tokenMatch = output.match(/sk-ant-oat\d*-[\w-]+/);
|
|
169
|
+
if (result.stderr) process.stderr.write(result.stderr);
|
|
142
170
|
|
|
143
|
-
|
|
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 && !
|
|
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
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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.
|
|
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
|
|
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",
|