client-handover 1.4.0 → 1.5.0

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 +48 -141
  2. package/cli.js +202 -174
  3. package/generator.js +52 -8
  4. package/package.json +2 -2
package/README.md CHANGED
@@ -6,183 +6,87 @@
6
6
 
7
7
  AI-powered handover document generator for frontend developers handing off websites to clients.
8
8
 
9
- Run one command. Get a professional handover document in Markdown, plain text, and HTML — written for both your client and the next developer.
9
+ Run one command from inside your project. Get professional handover documents in Markdown, plain text, and Word — written for both your client and the next developer.
10
10
 
11
- Uses the [Claude API](https://console.anthropic.com) (Anthropic) under the hood.
11
+ Powered by [Claude Code](https://claude.ai) no API key or credits required.
12
12
 
13
13
  ---
14
14
 
15
- ## Install
15
+ ## Requirements
16
16
 
17
- ```bash
18
- npm install -g client-handover
19
- ```
17
+ - Node.js 18+
18
+ - [Claude Code](https://claude.ai/download) installed and logged in (`claude login`)
20
19
 
21
- ### API key setup
20
+ Claude Code is free with a Claude Pro subscription.
22
21
 
23
- **If you have Claude Code installed**, no setup needed — `client-handover` will automatically use your existing Claude credentials.
22
+ ---
24
23
 
25
- **Otherwise**, set your Anthropic API key as an environment variable:
24
+ ## Install
26
25
 
27
26
  ```bash
28
- # macOS / Linux
29
- export ANTHROPIC_API_KEY=your_key_here
30
-
31
- # Windows (Command Prompt)
32
- set ANTHROPIC_API_KEY=your_key_here
33
-
34
- # Windows (PowerShell)
35
- $env:ANTHROPIC_API_KEY="your_key_here"
27
+ npm install -g client-handover
36
28
  ```
37
29
 
38
- Get a free API key at [console.anthropic.com](https://console.anthropic.com)
39
-
40
30
  ---
41
31
 
42
32
  ## Quick start
43
33
 
44
34
  ```bash
45
- handover handover
35
+ cd my-client-project
36
+ handover /create
46
37
  ```
47
38
 
48
- This generates a full handover document with placeholder content in an `output/` folder in your current directory.
49
-
50
- ---
51
-
52
- ## Commands
53
-
54
- | Command | Description |
55
- |---------|-------------|
56
- | `all` | Every section as separate files in one folder |
57
- | `handover` | Full handover document (all sections combined) |
58
- | `setup` | Project setup & dependencies |
59
- | `deploy` | Deployment & hosting info |
60
- | `credentials` | Logins & API keys template |
61
- | `license` | Licensing & attribution |
39
+ On first run you'll be asked for your name, company, email, and phone — these appear in every document you generate.
62
40
 
63
41
  ---
64
42
 
65
43
  ## Usage
66
44
 
67
- ### With your own project notes
68
-
69
- Create a plain text file describing your project:
70
-
71
- ```
72
- project-info.txt
73
- ----------------
74
- Project: Acme Corp website
75
- Stack: Vue 3, Vite, Netlify
76
- Repo: https://github.com/you/acme
77
- Live URL: https://acmecorp.com
78
- Hosting: Netlify (free tier)
79
- Domain: Namecheap, auto-renews Jan 2026
80
- CMS: Netlify CMS
81
- Analytics: Google Analytics 4
82
- APIs: Mailchimp (newsletter), Stripe (payments)
83
- ```
84
-
85
- Then run any command with that file as input:
86
-
87
45
  ```bash
88
- # Every section as separate files in one folder
89
- handover all project-info.txt acme-client
90
-
91
- # Full combined doc
92
- handover handover project-info.txt
93
-
94
- # Full combined doc with a custom output filename
95
- handover handover project-info.txt acme-handover
96
-
97
- # Individual sections
98
- handover setup project-info.txt
99
- handover deploy project-info.txt
100
- handover credentials project-info.txt
101
- handover license project-info.txt
46
+ handover /create
102
47
  ```
103
48
 
104
- The more detail you put in your project-info file, the more accurate and useful the output will be.
49
+ Choose from:
50
+ - `1` — Technical handover (for the next developer)
51
+ - `2` — Client handover (plain English, for your client)
52
+ - `3` — Both
53
+
54
+ The tool scans your project automatically — no config file needed.
105
55
 
106
56
  ---
107
57
 
108
58
  ## Output
109
59
 
110
- All commands generate three files per section inside an `output/` folder.
60
+ Documents are saved in your project folder:
111
61
 
112
- **Single command** (e.g. `handover`):
113
- ```
114
- output/
115
- ├── handover.md ← Paste into Notion, GitHub, or a README
116
- ├── handover.txt ← Clean plain text for email or printing
117
- └── handover.html ← Styled HTML ready to send directly to a client
118
62
  ```
63
+ technical-handover/
64
+ ├── technical-handover.md
65
+ ├── technical-handover.txt
66
+ └── technical-handover.docx
119
67
 
120
- **`all` command** — every section in its own subfolder:
121
- ```
122
- output/acme-client/
123
- ├── handover.md / .txt / .html
124
- ├── setup.md / .txt / .html
125
- ├── deploy.md / .txt / .html
126
- ├── credentials.md / .txt / .html
127
- └── license.md / .txt / .html
68
+ client-handover/
69
+ ├── client-handover.md
70
+ ├── client-handover.txt
71
+ └── client-handover.docx
128
72
  ```
129
73
 
130
74
  | Format | Best for |
131
75
  |--------|----------|
132
76
  | `.md` | Notion, GitHub, linear docs |
133
77
  | `.txt` | Email attachments, printing |
134
- | `.html`| Sending directly to a client |
135
-
136
- ---
137
-
138
- ## Use as a library
139
-
140
- You can also import the prompt builders and doc generator directly into your own project:
141
-
142
- ```js
143
- import { handover, setup, generateDoc } from 'client-handover'
144
-
145
- // Build a prompt from your project info string
146
- const prompt = handover('Vue 3 project hosted on Vercel, domain on Cloudflare...')
147
-
148
- // Generate and save the document
149
- await generateDoc(prompt, 'my-client', './docs')
150
- ```
151
-
152
- ### Available exports
153
-
154
- | Export | Type | Description |
155
- |--------|------|-------------|
156
- | `generateDoc(prompt, name, dir)` | async function | Calls Claude API and writes `.md`, `.txt`, `.html` |
157
- | `handover(projectInfo)` | function | Prompt builder for full handover doc |
158
- | `setup(projectInfo)` | function | Prompt builder for setup section |
159
- | `deploy(projectInfo)` | function | Prompt builder for deployment section |
160
- | `credentials(projectInfo)` | function | Prompt builder for credentials section |
161
- | `license(projectInfo)` | function | Prompt builder for licensing section |
162
-
163
- All prompt builders accept an optional `projectInfo` string. If omitted, Claude generates placeholder content.
164
-
165
- ---
166
-
167
- ## Requirements
168
-
169
- - Node.js 18+
170
- - One of the following:
171
- - [Claude Code](https://marketplace.visualstudio.com/items?itemName=anthropic.claude-code) installed (zero config — credentials detected automatically)
172
- - Or an Anthropic API key — [get one free at console.anthropic.com](https://console.anthropic.com)
78
+ | `.docx`| Sending directly to a client |
173
79
 
174
80
  ---
175
81
 
176
82
  ## How it works
177
83
 
178
- 1. You provide a plain text description of your project (or nothing, for placeholder output)
179
- 2. The CLI builds a structured prompt for Claude
180
- 3. Claude generates a professional, dual-audience document (plain English for clients, technical detail for developers)
181
- 4. The output is saved as `.md`, `.txt`, and `.html`
182
-
183
- Each document section is written for **two audiences**:
184
- - **The client** — plain English, reassuring tone, no jargon
185
- - **The next developer** — precise technical detail, commands, file paths
84
+ 1. The tool scans your project `package.json`, config files, env variable keys, deploy configs, folder structure, CSS colours
85
+ 2. It builds a structured prompt and sends it to Claude via Claude Code
86
+ 3. Claude generates a professional document written for two audiences:
87
+ - **The client** plain English, reassuring tone, no jargon
88
+ - **The next developer** — precise technical detail, commands, file paths
89
+ 4. Output is saved as `.md`, `.txt`, and `.docx`
186
90
 
187
91
  ---
188
92
 
@@ -190,28 +94,31 @@ Each document section is written for **two audiences**:
190
94
 
191
95
  ```
192
96
  client-handover/
193
- ├── cli.js # CLI entry point
194
- ├── index.js # Library exports
195
- ├── generator.js # Claude API call + file output
196
- ├── handover.js # Full handover prompt builder
197
- ├── setup.js # Setup section prompt builder
198
- ├── deploy.js # Deployment section prompt builder
199
- ├── credentials.js # Credentials section prompt builder
200
- └── license.js # Licensing section prompt builder
97
+ ├── cli.js # CLI entry point
98
+ ├── generator.js # Claude Code SDK call + file output
99
+ ├── prompts.js # Prompt builders
100
+ └── scanner.js # Project file scanner
201
101
  ```
202
102
 
203
103
  ---
204
104
 
205
105
  ## Changelog
206
106
 
107
+ ### 1.4.0
108
+ - Switched from Anthropic API to Claude Code SDK — no API key or credits required
109
+ - Removed `handover key` command and API key setup prompt
110
+ - Claude Code is now installed automatically as a dependency
111
+
112
+ ### 1.3.0
113
+ - Improved setup flow and document type selection
114
+
207
115
  ### 1.0.0
208
116
  - Initial release
209
117
  - Single command: `handover /create`
210
118
  - Generates two documents per run — technical handover (for developers) and client handover (plain English)
211
119
  - Auto-scans project files: package.json, config files, env variable keys, deploy configs, folder structure, CSS colours
212
- - First-run setup prompts for API key, name, company, email, and phone
120
+ - First-run setup prompts for name, company, email, and phone
213
121
  - Outputs `.md`, `.txt`, and `.docx` formats
214
- - Auto-detects Claude Code credentials — no API key setup needed if you have Claude Code installed
215
122
 
216
123
  ---
217
124
 
package/cli.js CHANGED
@@ -1,174 +1,202 @@
1
- #!/usr/bin/env node
2
-
3
- import { generateDoc } from './generator.js'
4
- import { technicalHandoverPrompt, nonTechnicalHandoverPrompt } from './prompts.js'
5
- import { scanProject } from './scanner.js'
6
- import chalk from 'chalk'
7
- import readline from 'readline'
8
- import fs from 'fs'
9
- import path from 'path'
10
- import os from 'os'
11
-
12
- const { version } = JSON.parse(fs.readFileSync(new URL('./package.json', import.meta.url), 'utf-8'))
13
-
14
- function normalizeCommand(cmd) {
15
- return path.basename(cmd).replace(/^\/+/, '')
16
- }
17
-
18
- function printBanner() {
19
- const t = chalk.bold.white
20
- console.log()
21
- console.log(' ' + t('█ █ ███ █ █ ████ ███ █ █ █████ ████ '))
22
- console.log(' ' + t('█ █ █ █ ██ █ █ █ █ █ █ █ █ █ █'))
23
- console.log(' ' + t('█████ █████ █ █ █ █ █ █ █ █ █ ████ ████ '))
24
- console.log(' ' + t('█ █ █ █ █ ██ █ █ █ █ █ █ █ █ █ '))
25
- console.log(' ' + t('█ █ █ █ █ █ ████ ███ █ █████ █ █ '))
26
- console.log()
27
- console.log(chalk.dim(' Website handover documents for clients & developers') + chalk.dim(' · v' + version))
28
- console.log()
29
- }
30
-
31
- function printHelp() {
32
- printBanner()
33
- console.log(chalk.dim(' Usage:'))
34
- console.log(' handover /create Generate technical & non-technical handover documents\n')
35
- console.log(chalk.dim(' Example:'))
36
- console.log(' cd my-client-project')
37
- console.log(' handover /create\n')
38
- }
39
-
40
- function ask(rl, question) {
41
- return new Promise(resolve => rl.question(question, answer => resolve(answer.trim())))
42
- }
43
-
44
-
45
- async function runSetup() {
46
- const configDir = path.join(os.homedir(), '.handover')
47
- const configPath = path.join(configDir, 'config.json')
48
-
49
- let config = {}
50
- try {
51
- if (fs.existsSync(configPath)) config = JSON.parse(fs.readFileSync(configPath, 'utf-8'))
52
- } catch {}
53
-
54
- const hasDeveloperInfo = !!config.developerName
55
-
56
- if (hasDeveloperInfo) {
57
- return {
58
- name: config.developerName,
59
- company: config.developerCompany || '',
60
- email: config.developerEmail || '',
61
- phone: config.developerPhone || '',
62
- }
63
- }
64
-
65
- console.log(chalk.bold('\n──────────────────────────────────────────'))
66
- console.log(chalk.bold(' client-handover — first time setup'))
67
- console.log(chalk.bold('──────────────────────────────────────────\n'))
68
-
69
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
70
-
71
- if (!hasDeveloperInfo) {
72
- console.log(' Your details will appear in all generated handover documents.\n')
73
- const name = await ask(rl, ' Developer name: ')
74
- if (name) config.developerName = name
75
-
76
- const company = await ask(rl, ' Company name (press Enter if not applicable): ')
77
- if (company) config.developerCompany = company
78
-
79
- const email = await ask(rl, ' Email address: ')
80
- if (email) config.developerEmail = email
81
-
82
- const phone = await ask(rl, ' Phone number (optional, press Enter to skip): ')
83
- if (phone) config.developerPhone = phone
84
-
85
- console.log()
86
- }
87
-
88
- rl.close()
89
-
90
- if (!fs.existsSync(configDir)) fs.mkdirSync(configDir, { recursive: true })
91
- fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8')
92
- console.log(chalk.green(' ✓ Setup saved.\n'))
93
-
94
- return {
95
- name: config.developerName || '',
96
- company: config.developerCompany || '',
97
- email: config.developerEmail || '',
98
- phone: config.developerPhone || '',
99
- }
100
- }
101
-
102
- async function runCreate() {
103
- printBanner()
104
- const developerInfo = await runSetup()
105
-
106
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
107
- let docType = ''
108
- while (!['1', '2', '3'].includes(docType)) {
109
- docType = await ask(rl, ' What would you like to generate?\n 1 - Technical handover\n 2 - Non-technical handover\n 3 - Both\n\n Enter 1, 2 or 3: ')
110
- if (!['1', '2', '3'].includes(docType)) console.log(chalk.yellow('\n Please enter 1, 2 or 3.\n'))
111
- }
112
- rl.close()
113
- console.log()
114
-
115
- console.log(chalk.dim(' Scanning project files...'))
116
- const projectInfo = scanProject(process.cwd())
117
-
118
- const techDir = './technical-handover'
119
- const clientDir = './client-handover'
120
-
121
- if (docType === '1' || docType === '3') {
122
- console.log(chalk.bold('\n Generating Technical Handover Document...'))
123
- const techPrompt = technicalHandoverPrompt(projectInfo, developerInfo)
124
- await generateDoc(techPrompt, 'technical-handover', techDir)
125
- }
126
-
127
- if (docType === '2' || docType === '3') {
128
- console.log(chalk.bold('\n Generating Client Handover Document...'))
129
- const nonTechPrompt = nonTechnicalHandoverPrompt(projectInfo, developerInfo)
130
- await generateDoc(nonTechPrompt, 'client-handover', clientDir)
131
- }
132
-
133
- console.log(chalk.green.bold('\n Handover documents created successfully!\n'))
134
- if (docType === '1' || docType === '3') {
135
- console.log(` ${chalk.cyan('./technical-handover/')}`)
136
- console.log(` technical-handover.md`)
137
- console.log(` technical-handover.txt`)
138
- console.log(` technical-handover.docx`)
139
- console.log()
140
- }
141
- if (docType === '2' || docType === '3') {
142
- console.log(` ${chalk.cyan('./client-handover/')}`)
143
- console.log(` client-handover.md`)
144
- console.log(` client-handover.txt`)
145
- console.log(` client-handover.docx`)
146
- console.log()
147
- }
148
- }
149
-
150
- async function main() {
151
- const [,, rawCommand, secondArg] = process.argv
152
-
153
- if (!rawCommand || rawCommand === '--help' || rawCommand === '-h') {
154
- printHelp()
155
- process.exit(0)
156
- }
157
-
158
- const command = normalizeCommand(rawCommand)
159
-
160
- if (command !== 'create') {
161
- console.error(chalk.red(`\n ❌ Unknown command: ${rawCommand}\n`))
162
- printHelp()
163
- process.exit(1)
164
- }
165
-
166
- try {
167
- await runCreate()
168
- } catch (err) {
169
- console.error(chalk.red(`\n ❌ Error: ${err.message}\n`))
170
- process.exit(1)
171
- }
172
- }
173
-
174
- main()
1
+ #!/usr/bin/env node
2
+
3
+ import { generateDoc, resolveApiKey, saveApiKey } from './generator.js'
4
+ import { technicalHandoverPrompt, nonTechnicalHandoverPrompt } from './prompts.js'
5
+ import { scanProject } from './scanner.js'
6
+ import chalk from 'chalk'
7
+ import readline from 'readline'
8
+ import fs from 'fs'
9
+ import path from 'path'
10
+ import os from 'os'
11
+
12
+ const { version } = JSON.parse(fs.readFileSync(new URL('./package.json', import.meta.url), 'utf-8'))
13
+
14
+ function normalizeCommand(cmd) {
15
+ return path.basename(cmd).replace(/^\/+/, '')
16
+ }
17
+
18
+ function printBanner() {
19
+ const t = chalk.bold.white
20
+ console.log()
21
+ console.log(' ' + t('█ █ ███ █ █ ████ ███ █ █ █████ ████ '))
22
+ console.log(' ' + t('█ █ █ █ ██ █ █ █ █ █ █ █ █ █ █'))
23
+ console.log(' ' + t('█████ █████ █ █ █ █ █ █ █ █ █ ████ ████ '))
24
+ console.log(' ' + t('█ █ █ █ █ ██ █ █ █ █ █ █ █ █ █ '))
25
+ console.log(' ' + t('█ █ █ █ █ █ ████ ███ █ █████ █ █ '))
26
+ console.log()
27
+ console.log(chalk.dim(' Website handover documents for clients & developers') + chalk.dim(' · v' + version))
28
+ console.log()
29
+ }
30
+
31
+ function printHelp() {
32
+ printBanner()
33
+ console.log(chalk.dim(' Usage:'))
34
+ console.log(' handover /create Generate technical & non-technical handover documents')
35
+ console.log(' handover key <key> Save your Anthropic API key\n')
36
+ console.log(chalk.dim(' Example:'))
37
+ console.log(' cd my-client-project')
38
+ console.log(' handover /create\n')
39
+ }
40
+
41
+ function ask(rl, question) {
42
+ return new Promise(resolve => rl.question(question, answer => resolve(answer.trim())))
43
+ }
44
+
45
+
46
+ async function runSetup() {
47
+ const configDir = path.join(os.homedir(), '.handover')
48
+ const configPath = path.join(configDir, 'config.json')
49
+
50
+ let config = {}
51
+ try {
52
+ if (fs.existsSync(configPath)) config = JSON.parse(fs.readFileSync(configPath, 'utf-8'))
53
+ } catch {}
54
+
55
+ const hasApiKey = !!resolveApiKey(config)
56
+ const hasDeveloperInfo = !!config.developerName
57
+
58
+ if (hasApiKey && hasDeveloperInfo) {
59
+ return {
60
+ name: config.developerName,
61
+ company: config.developerCompany || '',
62
+ email: config.developerEmail || '',
63
+ phone: config.developerPhone || '',
64
+ }
65
+ }
66
+
67
+ console.log(chalk.bold('\n──────────────────────────────────────────'))
68
+ console.log(chalk.bold(' client-handover — first time setup'))
69
+ console.log(chalk.bold('──────────────────────────────────────────\n'))
70
+
71
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
72
+
73
+ if (!hasApiKey) {
74
+ console.log(' To generate documents you need an Anthropic API key.')
75
+ console.log(chalk.dim(' Get one free at: https://console.anthropic.com\n'))
76
+ let key = ''
77
+ while (!key) {
78
+ key = await ask(rl, ' Enter your Anthropic API key: ')
79
+ if (!key) console.log(chalk.yellow(' API key is required to continue.\n'))
80
+ }
81
+ config.apiKey = key
82
+ console.log(chalk.green(' API key saved.\n'))
83
+ }
84
+
85
+ if (!hasDeveloperInfo) {
86
+ console.log(' Your details will appear in all generated handover documents.\n')
87
+ const name = await ask(rl, ' Developer name: ')
88
+ if (name) config.developerName = name
89
+
90
+ const company = await ask(rl, ' Company name (press Enter if not applicable): ')
91
+ if (company) config.developerCompany = company
92
+
93
+ const email = await ask(rl, ' Email address: ')
94
+ if (email) config.developerEmail = email
95
+
96
+ const phone = await ask(rl, ' Phone number (optional, press Enter to skip): ')
97
+ if (phone) config.developerPhone = phone
98
+
99
+ console.log()
100
+ }
101
+
102
+ rl.close()
103
+
104
+ if (!fs.existsSync(configDir)) fs.mkdirSync(configDir, { recursive: true })
105
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8')
106
+ console.log(chalk.green(' Setup saved.\n'))
107
+
108
+ return {
109
+ name: config.developerName || '',
110
+ company: config.developerCompany || '',
111
+ email: config.developerEmail || '',
112
+ phone: config.developerPhone || '',
113
+ }
114
+ }
115
+
116
+ async function runCreate() {
117
+ printBanner()
118
+ const developerInfo = await runSetup()
119
+
120
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
121
+ let docType = ''
122
+ while (!['1', '2', '3'].includes(docType)) {
123
+ docType = await ask(rl, ' What would you like to generate?\n 1 - Technical handover\n 2 - Non-technical handover\n 3 - Both\n\n Enter 1, 2 or 3: ')
124
+ if (!['1', '2', '3'].includes(docType)) console.log(chalk.yellow('\n Please enter 1, 2 or 3.\n'))
125
+ }
126
+ rl.close()
127
+ console.log()
128
+
129
+ console.log(chalk.dim(' Scanning project files...'))
130
+ const projectInfo = scanProject(process.cwd())
131
+
132
+ const techDir = './technical-handover'
133
+ const clientDir = './client-handover'
134
+
135
+ if (docType === '1' || docType === '3') {
136
+ console.log(chalk.bold('\n Generating Technical Handover Document...'))
137
+ const techPrompt = technicalHandoverPrompt(projectInfo, developerInfo)
138
+ await generateDoc(techPrompt, 'technical-handover', techDir)
139
+ }
140
+
141
+ if (docType === '2' || docType === '3') {
142
+ console.log(chalk.bold('\n Generating Client Handover Document...'))
143
+ const nonTechPrompt = nonTechnicalHandoverPrompt(projectInfo, developerInfo)
144
+ await generateDoc(nonTechPrompt, 'client-handover', clientDir)
145
+ }
146
+
147
+ console.log(chalk.green.bold('\n ✅ Handover documents created successfully!\n'))
148
+ if (docType === '1' || docType === '3') {
149
+ console.log(` ${chalk.cyan('./technical-handover/')}`)
150
+ console.log(` technical-handover.md`)
151
+ console.log(` technical-handover.txt`)
152
+ console.log(` technical-handover.docx`)
153
+ console.log()
154
+ }
155
+ if (docType === '2' || docType === '3') {
156
+ console.log(` ${chalk.cyan('./client-handover/')}`)
157
+ console.log(` client-handover.md`)
158
+ console.log(` client-handover.txt`)
159
+ console.log(` client-handover.docx`)
160
+ console.log()
161
+ }
162
+ }
163
+
164
+ async function main() {
165
+ const [,, rawCommand, secondArg] = process.argv
166
+
167
+ if (!rawCommand || rawCommand === '--help' || rawCommand === '-h') {
168
+ printHelp()
169
+ process.exit(0)
170
+ }
171
+
172
+ const command = normalizeCommand(rawCommand)
173
+
174
+ if (command === 'key') {
175
+ if (!secondArg) {
176
+ console.error(chalk.red('\n ❌ Usage: handover key <your-api-key>\n'))
177
+ process.exit(1)
178
+ }
179
+ saveApiKey(secondArg)
180
+ console.log(chalk.green('\n ✅ API key saved. Run "handover /create" to get started.\n'))
181
+ process.exit(0)
182
+ }
183
+
184
+ if (command !== 'create') {
185
+ console.error(chalk.red(`\n ❌ Unknown command: ${rawCommand}\n`))
186
+ printHelp()
187
+ process.exit(1)
188
+ }
189
+
190
+ try {
191
+ await runCreate()
192
+ } catch (err) {
193
+ if (err.status === 401) {
194
+ console.error(chalk.red('\n ❌ Authentication failed. Run "handover key <your-api-key>" to set your API key.\n'))
195
+ } else {
196
+ console.error(chalk.red(`\n ❌ Error: ${err.message}\n`))
197
+ }
198
+ process.exit(1)
199
+ }
200
+ }
201
+
202
+ main()
package/generator.js CHANGED
@@ -1,4 +1,4 @@
1
- import { query } from '@anthropic-ai/claude-code'
1
+ import Anthropic from '@anthropic-ai/sdk'
2
2
  import {
3
3
  Document, Packer, Paragraph, TextRun, HeadingLevel,
4
4
  AlignmentType, BorderStyle, TableRow, TableCell, Table,
@@ -6,6 +6,51 @@ import {
6
6
  } from 'docx'
7
7
  import fs from 'fs'
8
8
  import path from 'path'
9
+ import os from 'os'
10
+
11
+ export function resolveApiKey(config = null) {
12
+ if (process.env.ANTHROPIC_API_KEY) return process.env.ANTHROPIC_API_KEY
13
+
14
+ const cfg = config || (() => {
15
+ try {
16
+ const p = path.join(os.homedir(), '.handover', 'config.json')
17
+ if (fs.existsSync(p)) return JSON.parse(fs.readFileSync(p, 'utf-8'))
18
+ } catch {}
19
+ return {}
20
+ })()
21
+
22
+ if (cfg?.apiKey) return cfg.apiKey
23
+
24
+ const credentialsPath = path.join(os.homedir(), '.claude', '.credentials.json')
25
+ try {
26
+ if (fs.existsSync(credentialsPath)) {
27
+ const creds = JSON.parse(fs.readFileSync(credentialsPath, 'utf-8'))
28
+ const token = creds?.claudeAiOauth?.accessToken
29
+ const expiresAt = creds?.claudeAiOauth?.expiresAt
30
+ if (token && (!expiresAt || expiresAt > Date.now())) return token
31
+ }
32
+ } catch {}
33
+
34
+ return null
35
+ }
36
+
37
+ export function saveApiKey(key) {
38
+ const configDir = path.join(os.homedir(), '.handover')
39
+ const configPath = path.join(configDir, 'config.json')
40
+ if (!fs.existsSync(configDir)) fs.mkdirSync(configDir, { recursive: true })
41
+ let config = {}
42
+ try { config = JSON.parse(fs.readFileSync(configPath, 'utf-8')) } catch {}
43
+ config.apiKey = key
44
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8')
45
+ }
46
+
47
+ const apiKey = resolveApiKey()
48
+ if (!apiKey) {
49
+ console.error('\n ❌ No API key found. Run "handover key <your-api-key>" or install Claude Code.\n')
50
+ process.exit(1)
51
+ }
52
+
53
+ const client = new Anthropic({ apiKey })
9
54
 
10
55
  // ---------------------------------------------------------------------------
11
56
  // Inline text parser — splits a line into TextRun segments handling **bold**,
@@ -253,14 +298,13 @@ function buildDocx(markdown) {
253
298
  export async function generateDoc(prompt, outputName = 'handover', outputDir = './output') {
254
299
  console.log('⏳ Generating document with Claude...\n')
255
300
 
256
- let markdown = ''
257
- for await (const message of query({ prompt, options: { maxTurns: 1 } })) {
258
- if (message.type === 'result' && message.subtype === 'success') {
259
- markdown = message.result
260
- }
261
- }
301
+ const message = await client.messages.create({
302
+ model: 'claude-sonnet-4-6',
303
+ max_tokens: 4096,
304
+ messages: [{ role: 'user', content: prompt }]
305
+ })
262
306
 
263
- if (!markdown) throw new Error('No response received from Claude')
307
+ const markdown = message.content[0].text
264
308
 
265
309
  if (!fs.existsSync(outputDir)) {
266
310
  fs.mkdirSync(outputDir, { recursive: true })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "client-handover",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "AI-powered handover document generator for frontend developers handing off client websites",
5
5
  "type": "module",
6
6
  "bin": {
@@ -29,7 +29,7 @@
29
29
  "node": ">=18"
30
30
  },
31
31
  "dependencies": {
32
- "@anthropic-ai/claude-code": "latest",
32
+ "@anthropic-ai/sdk": "^0.39.0",
33
33
  "chalk": "^5.3.0",
34
34
  "docx": "^8.5.0"
35
35
  }