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.
- package/README.md +48 -141
- package/cli.js +202 -174
- package/generator.js +52 -8
- 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
|
|
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
|
-
|
|
11
|
+
Powered by [Claude Code](https://claude.ai) — no API key or credits required.
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
15
|
-
##
|
|
15
|
+
## Requirements
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
```
|
|
17
|
+
- Node.js 18+
|
|
18
|
+
- [Claude Code](https://claude.ai/download) installed and logged in (`claude login`)
|
|
20
19
|
|
|
21
|
-
|
|
20
|
+
Claude Code is free with a Claude Pro subscription.
|
|
22
21
|
|
|
23
|
-
|
|
22
|
+
---
|
|
24
23
|
|
|
25
|
-
|
|
24
|
+
## Install
|
|
26
25
|
|
|
27
26
|
```bash
|
|
28
|
-
|
|
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
|
-
|
|
35
|
+
cd my-client-project
|
|
36
|
+
handover /create
|
|
46
37
|
```
|
|
47
38
|
|
|
48
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
| `.
|
|
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.
|
|
179
|
-
2.
|
|
180
|
-
3. Claude generates a professional
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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
|
|
194
|
-
├──
|
|
195
|
-
├──
|
|
196
|
-
|
|
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
|
|
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
|
|
35
|
-
console.log(
|
|
36
|
-
console.log('
|
|
37
|
-
console.log('
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
console.log(chalk.bold('
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
console.log(
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
if (docType === '2' || docType === '3') {
|
|
142
|
-
console.log(
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
|
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
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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
|
-
|
|
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.
|
|
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/
|
|
32
|
+
"@anthropic-ai/sdk": "^0.39.0",
|
|
33
33
|
"chalk": "^5.3.0",
|
|
34
34
|
"docx": "^8.5.0"
|
|
35
35
|
}
|