@slahon/lazykit 1.2.9 → 1.3.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.
Files changed (4) hide show
  1. package/README.md +15 -4
  2. package/git.js +10 -1
  3. package/init.js +54 -35
  4. package/package.json +1 -1
package/README.md CHANGED
@@ -21,12 +21,14 @@ Run this from your **project's root directory** — the same folder that contain
21
21
  - Node.js 18+
22
22
  - A GitHub repository with a remote set up (`git remote -v` should show a GitHub URL)
23
23
  - A Claude Pro or Max subscription ([claude.ai](https://claude.ai))
24
- - Claude Code installed locally (`npm install -g @anthropic-ai/claude-code`)
25
- - GitHub CLI (`gh`) — required for full automation (`brew install gh` / [cli.github.com](https://cli.github.com))
24
+ - **Claude Code CLI** `npm install -g @anthropic-ai/claude-code`
25
+ - **GitHub CLI (`gh`)** — `brew install gh` or [cli.github.com](https://cli.github.com), then `gh auth login`
26
26
  - **Claude Code GitHub App** installed on your repo — [github.com/apps/claude](https://github.com/apps/claude) *(required for the Actions workflow to run)*
27
27
 
28
28
  > **All `npx @slahon/lazykit` commands must be run from your project's root directory** — the folder where your `.git` directory lives and your GitHub remote is configured.
29
29
 
30
+ `init` checks for `gh` and `claude` before proceeding. If either is missing or not authenticated, it will show you exactly what to install and wait for you to confirm before continuing — no need to restart.
31
+
30
32
  ## How it works
31
33
 
32
34
  ### 1. Open an issue using the LazyKit Task template
@@ -137,9 +139,18 @@ During `npx @slahon/lazykit@latest init` you will be asked:
137
139
 
138
140
  LazyKit uses your Claude Pro/Max subscription via an OAuth token — no pay-per-token API billing.
139
141
 
140
- `init` handles this automatically: it runs `claude setup-token`, captures the token, and stores it as `CLAUDE_CODE_OAUTH_TOKEN` in your repo secrets via `gh secret set`.
142
+ During `init`, LazyKit runs `claude setup-token` and tries to capture the token automatically. On many systems a browser window opens, you approve access, and the token is stored as `CLAUDE_CODE_OAUTH_TOKEN` in your repo secrets without any extra steps.
143
+
144
+ If the token can't be captured automatically (varies by system), LazyKit falls back and asks you to paste it:
145
+
146
+ ```
147
+ ⚠ Could not capture the token automatically.
148
+ If a browser opened, complete the auth and copy the token it shows.
149
+
150
+ Paste your token here (or press Enter to skip) ›
151
+ ```
141
152
 
142
- If the token can't be set automatically (Claude Code not installed or `gh` not available), you'll see step-by-step instructions to do it manually.
153
+ Just paste the token from your terminal and LazyKit sets the secret for you. If you skip, you'll get step-by-step instructions to add it manually.
143
154
 
144
155
  **Token expiry:** OAuth tokens can expire. Run `lazykit status` to check the age of your token. If it's expired, re-run `npx @slahon/lazykit@latest init` to generate and store a fresh one.
145
156
 
package/git.js CHANGED
@@ -91,6 +91,15 @@ function detectStack() {
91
91
  return stack.length > 0 ? stack.join(' + ') : null;
92
92
  }
93
93
 
94
+ function isClaudeAvailable() {
95
+ try {
96
+ execSync('claude --version', { stdio: 'ignore' });
97
+ return true;
98
+ } catch {
99
+ return false;
100
+ }
101
+ }
102
+
94
103
  function hasBranchProtection({ owner, repo }) {
95
104
  try {
96
105
  execSync(`gh api repos/${owner}/${repo}/branches/main/protection`, { stdio: 'pipe' });
@@ -100,4 +109,4 @@ function hasBranchProtection({ owner, repo }) {
100
109
  }
101
110
  }
102
111
 
103
- module.exports = { getGitRemote, parseGitHubRepo, isGitRepo, isGhCliAvailable, isGhAuthenticated, detectStack, hasBranchProtection };
112
+ module.exports = { getGitRemote, parseGitHubRepo, isGitRepo, isGhCliAvailable, isGhAuthenticated, isClaudeAvailable, detectStack, hasBranchProtection };
package/init.js CHANGED
@@ -7,8 +7,8 @@ const chalk = require('chalk');
7
7
  const ora = require('ora');
8
8
 
9
9
  const { log } = require('./logger');
10
- const { confirm } = require('./prompt');
11
- const { isGitRepo, getGitRemote, parseGitHubRepo, isGhCliAvailable, isGhAuthenticated, detectStack, hasBranchProtection } = require('./git');
10
+ const { ask, confirm } = require('./prompt');
11
+ const { isGitRepo, getGitRemote, parseGitHubRepo, isGhCliAvailable, isGhAuthenticated, isClaudeAvailable, detectStack, hasBranchProtection } = require('./git');
12
12
  const { generateWorkflow } = require('./workflow');
13
13
  const { generateClaudeMd } = require('./claude-md');
14
14
  const { generateIssueTemplate } = require('./issue-template');
@@ -45,28 +45,38 @@ async function init({ dryRun = false } = {}) {
45
45
  process.exit(1);
46
46
  }
47
47
 
48
- // ─── Step 2: Check gh CLI and auth ───────────────────────────────────────
49
- const ghAvailable = isGhCliAvailable();
50
- let ghReady = false;
51
-
52
- if (!ghAvailable) {
53
- log.warn('GitHub CLI (gh) not found.');
54
- console.log(chalk.gray('\n LazyKit uses gh to create labels, set secrets, and enable PR permissions.'));
55
- console.log(chalk.gray(' Without it, those steps will be skipped and you will need to do them manually.\n'));
56
- console.log(chalk.gray(' Install gh from: ') + chalk.cyan('https://cli.github.com'));
57
- console.log(chalk.gray(' Then re-run: ') + chalk.cyan('npx @slahon/lazykit@latest init\n'));
58
- const cont = await confirm('Continue without gh? (some steps will be manual)', false);
59
- if (!cont) process.exit(0);
60
- } else if (!isGhAuthenticated()) {
61
- log.warn('GitHub CLI is not authenticated.');
62
- console.log(chalk.gray('\n Run the following to log in, then re-run LazyKit:\n'));
63
- console.log(chalk.cyan(' gh auth login'));
64
- console.log(chalk.gray('\n Then re-run: ') + chalk.cyan('npx @slahon/lazykit@latest init\n'));
65
- const cont = await confirm('Continue without gh auth? (some steps will be manual)', false);
66
- if (!cont) process.exit(0);
67
- } else {
68
- ghReady = true;
48
+ // ─── Step 2: Check dependencies (mandatory) ──────────────────────────────
49
+
50
+ // gh CLI
51
+ while (!isGhCliAvailable()) {
52
+ log.error('GitHub CLI (gh) is required but not found.');
53
+ console.log(chalk.gray('\n LazyKit uses gh to create labels, set secrets, and enable PR permissions.\n'));
54
+ console.log(chalk.gray(' Install it from: ') + chalk.cyan('https://cli.github.com'));
55
+ console.log(chalk.gray(' macOS: ') + chalk.cyan('brew install gh'));
56
+ console.log(chalk.gray(' Windows: ') + chalk.cyan('winget install --id GitHub.cli\n'));
57
+ await confirm('Press Enter once gh is installed to check again', true);
58
+ }
59
+ log.success('GitHub CLI (gh) found');
60
+
61
+ // gh auth
62
+ while (!isGhAuthenticated()) {
63
+ log.error('GitHub CLI is not authenticated.');
64
+ console.log(chalk.gray('\n Run this in your terminal, then come back:\n'));
65
+ console.log(chalk.cyan(' gh auth login\n'));
66
+ await confirm('Press Enter once you are logged in to check again', true);
69
67
  }
68
+ log.success('GitHub CLI authenticated');
69
+
70
+ // Claude Code CLI
71
+ while (!isClaudeAvailable()) {
72
+ log.error('Claude Code CLI is required but not found.');
73
+ console.log(chalk.gray('\n Install it with:\n'));
74
+ console.log(chalk.cyan(' npm install -g @anthropic-ai/claude-code\n'));
75
+ await confirm('Press Enter once Claude Code is installed to check again', true);
76
+ }
77
+ log.success('Claude Code CLI found');
78
+
79
+ const ghReady = true;
70
80
 
71
81
  log.blank();
72
82
 
@@ -197,22 +207,32 @@ async function init({ dryRun = false } = {}) {
197
207
  if (result.stderr) process.stderr.write(result.stderr);
198
208
 
199
209
  const output = (result.stdout || '') + (result.stderr || '');
200
- const tokenMatch = output.match(/sk-ant-oat[^\s]+/);
210
+ let token = (output.match(/sk-ant-oat[^\s]+/) || [])[0];
211
+
212
+ // Fallback: ask user to paste if auto-capture didn't work
213
+ if (!token) {
214
+ console.log();
215
+ log.warn('Could not capture the token automatically.');
216
+ console.log(chalk.gray(' If a browser opened, complete the auth and copy the token it shows.\n'));
217
+ const pasted = await ask('Paste your token here (or press Enter to skip)', '');
218
+ if (pasted && pasted.trim().startsWith('sk-ant-oat')) {
219
+ token = pasted.trim();
220
+ }
221
+ }
201
222
 
202
- if (tokenMatch && ghReady) {
223
+ if (token && ghReady) {
203
224
  const secretSpinner = ora({ text: 'Adding CLAUDE_CODE_OAUTH_TOKEN to GitHub secrets...', color: 'cyan' }).start();
204
225
  execSync(
205
- `gh secret set CLAUDE_CODE_OAUTH_TOKEN --body "${tokenMatch[0]}" --repo ${repoInfo.owner}/${repoInfo.repo}`,
226
+ `gh secret set CLAUDE_CODE_OAUTH_TOKEN --body "${token}" --repo ${repoInfo.owner}/${repoInfo.repo}`,
206
227
  { stdio: 'pipe' }
207
228
  );
208
229
  secretSpinner.succeed(chalk.green('CLAUDE_CODE_OAUTH_TOKEN added to GitHub secrets'));
209
- } else if (tokenMatch && !ghReady) {
210
- log.warn('Token generated but gh CLI not available — add it manually:');
211
- console.log(chalk.gray(' Name: ') + chalk.cyan('CLAUDE_CODE_OAUTH_TOKEN'));
212
- console.log(chalk.gray(' Value: ') + chalk.cyan(tokenMatch[0]));
213
- console.log(chalk.cyan(` https://github.com/${repoInfo.owner}/${repoInfo.repo}/settings/secrets/actions`));
230
+ } else if (token && !ghReady) {
231
+ log.warn('gh CLI not available — add the secret manually:');
232
+ console.log(chalk.gray(' Go to: ') + chalk.cyan(`https://github.com/${repoInfo.owner}/${repoInfo.repo}/settings/secrets/actions`));
233
+ console.log(chalk.gray(' Add secret: ') + chalk.cyan('CLAUDE_CODE_OAUTH_TOKEN') + chalk.gray(' = ') + chalk.cyan(token));
214
234
  } else {
215
- throw new Error('Token not found in output');
235
+ throw new Error('No token');
216
236
  }
217
237
  } catch (err) {
218
238
  const isNotFound = (err.message || '').includes('ENOENT');
@@ -220,10 +240,9 @@ async function init({ dryRun = false } = {}) {
220
240
  log.warn('Claude Code CLI not found — install it first:');
221
241
  console.log(chalk.cyan('\n npm install -g @anthropic-ai/claude-code'));
222
242
  } else {
223
- log.warn('Could not set token automatically.');
243
+ log.warn('Token not set add it manually:');
224
244
  }
225
- console.log(chalk.gray('\n Add it manually:'));
226
- console.log(chalk.gray(' 1. Run: ') + chalk.cyan('claude setup-token'));
245
+ console.log(chalk.gray('\n 1. Run: ') + chalk.cyan('claude setup-token'));
227
246
  console.log(chalk.gray(' 2. Go to: ') + chalk.cyan(`https://github.com/${repoInfo.owner}/${repoInfo.repo}/settings/secrets/actions`));
228
247
  console.log(chalk.gray(' 3. Add secret: ') + chalk.cyan('CLAUDE_CODE_OAUTH_TOKEN') + chalk.gray(' = the token you copied\n'));
229
248
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slahon/lazykit",
3
- "version": "1.2.9",
3
+ "version": "1.3.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"