client-handover 1.0.5 → 1.1.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 +11 -0
- package/cli.js +87 -90
- package/generator.js +289 -104
- package/license.js +3 -2
- package/package.json +5 -8
- package/postinstall.js +63 -32
- package/prompts.js +185 -0
- package/scanner.js +250 -0
- package/credentials.js +0 -50
- package/deploy.js +0 -34
- package/handover.js +0 -96
- package/setup.js +0 -31
package/README.md
CHANGED
|
@@ -200,6 +200,17 @@ client-handover/
|
|
|
200
200
|
|
|
201
201
|
## Changelog
|
|
202
202
|
|
|
203
|
+
### 1.1.0
|
|
204
|
+
- Complete CLI redesign: all section commands replaced with a single `handover /create` command
|
|
205
|
+
- Now generates two separate documents per run — a technical handover (for the incoming developer) and a non-technical client guide (for the business owner)
|
|
206
|
+
- First-run developer info prompt — saves your name, company, email, and phone to config so every document is personalised
|
|
207
|
+
- CSS colour detection — automatically extracts hex colours and CSS custom properties from `.css`, `.scss`, `.less` files and Tailwind config
|
|
208
|
+
|
|
209
|
+
### 1.0.6
|
|
210
|
+
- Auto-scans your actual project files on every run — reads package.json, config files, env variable keys, deploy configs, folder structure, and more
|
|
211
|
+
- Generated documents are now tailored to your real project, not generic templates
|
|
212
|
+
- Extra notes file still supported as optional additional context
|
|
213
|
+
|
|
203
214
|
### 1.0.5
|
|
204
215
|
- Interactive API key setup prompt on install — no manual config needed
|
|
205
216
|
- Added `handover key <api-key>` command to save key at any time
|
package/cli.js
CHANGED
|
@@ -1,72 +1,103 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { handover } from './handover.js'
|
|
7
|
-
import { license } from './license.js'
|
|
8
|
-
import { generateDoc, saveApiKey } from './generator.js'
|
|
3
|
+
import { generateDoc, loadDeveloperInfo, saveApiKey } from './generator.js'
|
|
4
|
+
import { technicalHandoverPrompt, nonTechnicalHandoverPrompt } from './prompts.js'
|
|
5
|
+
import { scanProject } from './scanner.js'
|
|
9
6
|
import chalk from 'chalk'
|
|
7
|
+
import readline from 'readline'
|
|
10
8
|
import fs from 'fs'
|
|
11
9
|
import path from 'path'
|
|
12
|
-
|
|
13
|
-
const COMMANDS = {
|
|
14
|
-
'setup': { fn: setup, label: 'Project Setup & Dependencies' },
|
|
15
|
-
'deploy': { fn: deploy, label: 'Deployment & Hosting' },
|
|
16
|
-
'credentials': { fn: credentials, label: 'Credentials & Access' },
|
|
17
|
-
'handover': { fn: handover, label: 'Full Handover Document' },
|
|
18
|
-
'license': { fn: license, label: 'Licensing & Attribution' },
|
|
19
|
-
}
|
|
10
|
+
import os from 'os'
|
|
20
11
|
|
|
21
12
|
function normalizeCommand(cmd) {
|
|
22
|
-
// Git Bash converts /handover → C:\Program Files\Git\handover before Node sees it
|
|
23
|
-
// path.basename extracts just the last segment regardless of what form it arrives in
|
|
24
13
|
return path.basename(cmd).replace(/^\/+/, '')
|
|
25
14
|
}
|
|
26
15
|
|
|
27
16
|
function printHelp() {
|
|
28
|
-
console.log(chalk.bold('\n
|
|
17
|
+
console.log(chalk.bold('\n handover — Website Handover Document Generator\n'))
|
|
29
18
|
console.log(chalk.dim('Usage:'))
|
|
30
|
-
console.log(' handover
|
|
31
|
-
console.log(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
19
|
+
console.log(' handover /create Generate technical & non-technical handover documents')
|
|
20
|
+
console.log(' handover key <key> Save your Anthropic API key\n')
|
|
21
|
+
console.log(chalk.dim('Example:'))
|
|
22
|
+
console.log(' cd my-client-project')
|
|
23
|
+
console.log(' handover /create\n')
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function ask(rl, question) {
|
|
27
|
+
return new Promise(resolve => rl.question(question, answer => resolve(answer.trim())))
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function ensureDeveloperInfo() {
|
|
31
|
+
const configDir = path.join(os.homedir(), '.handover')
|
|
32
|
+
const configPath = path.join(configDir, 'config.json')
|
|
33
|
+
|
|
34
|
+
let config = {}
|
|
35
|
+
try {
|
|
36
|
+
if (fs.existsSync(configPath)) config = JSON.parse(fs.readFileSync(configPath, 'utf-8'))
|
|
37
|
+
} catch {}
|
|
38
|
+
|
|
39
|
+
if (config.developerName) {
|
|
40
|
+
return {
|
|
41
|
+
name: config.developerName,
|
|
42
|
+
company: config.developerCompany || '',
|
|
43
|
+
email: config.developerEmail || '',
|
|
44
|
+
phone: config.developerPhone || '',
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
console.log(chalk.bold('\n Your developer details will appear in the handover documents.\n'))
|
|
49
|
+
|
|
50
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
|
|
51
|
+
const name = await ask(rl, ' Your full name: ')
|
|
52
|
+
const company = await ask(rl, ' Your company name (or press Enter if not applicable): ')
|
|
53
|
+
const email = await ask(rl, ' Your email address: ')
|
|
54
|
+
const phone = await ask(rl, ' Your phone number (optional): ')
|
|
55
|
+
rl.close()
|
|
56
|
+
|
|
57
|
+
if (name) config.developerName = name
|
|
58
|
+
if (company) config.developerCompany = company
|
|
59
|
+
if (email) config.developerEmail = email
|
|
60
|
+
if (phone) config.developerPhone = phone
|
|
61
|
+
|
|
62
|
+
if (!fs.existsSync(configDir)) fs.mkdirSync(configDir, { recursive: true })
|
|
63
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8')
|
|
42
64
|
console.log()
|
|
65
|
+
|
|
66
|
+
return { name: name || '', company: company || '', email: email || '', phone: phone || '' }
|
|
43
67
|
}
|
|
44
68
|
|
|
45
|
-
async function
|
|
46
|
-
const
|
|
47
|
-
{ key: 'handover', fn: handover, label: 'Full Handover Document' },
|
|
48
|
-
{ key: 'setup', fn: setup, label: 'Project Setup & Dependencies' },
|
|
49
|
-
{ key: 'deploy', fn: deploy, label: 'Deployment & Hosting' },
|
|
50
|
-
{ key: 'credentials', fn: credentials, label: 'Credentials & Access' },
|
|
51
|
-
{ key: 'license', fn: license, label: 'Licensing & Attribution' },
|
|
52
|
-
]
|
|
69
|
+
async function runCreate() {
|
|
70
|
+
const developerInfo = await ensureDeveloperInfo()
|
|
53
71
|
|
|
54
|
-
|
|
72
|
+
console.log(chalk.dim(' Scanning project files...'))
|
|
73
|
+
const projectInfo = scanProject(process.cwd())
|
|
55
74
|
|
|
56
|
-
|
|
75
|
+
const techDir = './technical-handover'
|
|
76
|
+
const clientDir = './handover'
|
|
57
77
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
78
|
+
console.log(chalk.bold('\n Generating Technical Handover Document...'))
|
|
79
|
+
const techPrompt = technicalHandoverPrompt(projectInfo, developerInfo)
|
|
80
|
+
await generateDoc(techPrompt, 'technical-handover', techDir)
|
|
81
|
+
|
|
82
|
+
console.log(chalk.bold('\n Generating Client Handover Document...'))
|
|
83
|
+
const nonTechPrompt = nonTechnicalHandoverPrompt(projectInfo, developerInfo)
|
|
84
|
+
await generateDoc(nonTechPrompt, 'handover', clientDir)
|
|
64
85
|
|
|
65
|
-
console.log(chalk.green.bold(
|
|
86
|
+
console.log(chalk.green.bold('\n ✅ Handover documents created successfully!\n'))
|
|
87
|
+
console.log(` ${chalk.cyan('./technical-handover/')}`)
|
|
88
|
+
console.log(` technical-handover.md`)
|
|
89
|
+
console.log(` technical-handover.txt`)
|
|
90
|
+
console.log(` technical-handover.docx`)
|
|
91
|
+
console.log()
|
|
92
|
+
console.log(` ${chalk.cyan('./handover/')}`)
|
|
93
|
+
console.log(` handover.md`)
|
|
94
|
+
console.log(` handover.txt`)
|
|
95
|
+
console.log(` handover.docx`)
|
|
96
|
+
console.log()
|
|
66
97
|
}
|
|
67
98
|
|
|
68
99
|
async function main() {
|
|
69
|
-
const [,, rawCommand,
|
|
100
|
+
const [,, rawCommand, secondArg] = process.argv
|
|
70
101
|
|
|
71
102
|
if (!rawCommand || rawCommand === '--help' || rawCommand === '-h') {
|
|
72
103
|
printHelp()
|
|
@@ -76,62 +107,28 @@ async function main() {
|
|
|
76
107
|
const command = normalizeCommand(rawCommand)
|
|
77
108
|
|
|
78
109
|
if (command === 'key') {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
console.error(chalk.red('\n❌ Usage: handover key <your-api-key>\n'))
|
|
110
|
+
if (!secondArg) {
|
|
111
|
+
console.error(chalk.red('\n ❌ Usage: handover key <your-api-key>\n'))
|
|
82
112
|
process.exit(1)
|
|
83
113
|
}
|
|
84
|
-
saveApiKey(
|
|
85
|
-
console.log(chalk.green('\n✅ API key saved.
|
|
114
|
+
saveApiKey(secondArg)
|
|
115
|
+
console.log(chalk.green('\n ✅ API key saved. Run "handover /create" to get started.\n'))
|
|
86
116
|
process.exit(0)
|
|
87
117
|
}
|
|
88
118
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if (infoFile) {
|
|
92
|
-
if (!fs.existsSync(infoFile)) {
|
|
93
|
-
console.error(chalk.red(`\n❌ File not found: ${infoFile}\n`))
|
|
94
|
-
process.exit(1)
|
|
95
|
-
}
|
|
96
|
-
projectInfo = fs.readFileSync(infoFile, 'utf-8')
|
|
97
|
-
console.log(chalk.green(`\n📄 Loaded project info from: ${infoFile}`))
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if (command === 'all') {
|
|
101
|
-
const folderName = outputName || 'all'
|
|
102
|
-
try {
|
|
103
|
-
await runAll(projectInfo, folderName)
|
|
104
|
-
} catch (err) {
|
|
105
|
-
if (err.status === 401) {
|
|
106
|
-
console.error(chalk.red('\n❌ Authentication failed. Set ANTHROPIC_API_KEY or install Claude Code.\n'))
|
|
107
|
-
} else {
|
|
108
|
-
console.error(chalk.red(`\n❌ Error: ${err.message}\n`))
|
|
109
|
-
}
|
|
110
|
-
process.exit(1)
|
|
111
|
-
}
|
|
112
|
-
return
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const entry = COMMANDS[command]
|
|
116
|
-
if (!entry) {
|
|
117
|
-
console.error(chalk.red(`\n❌ Unknown command: ${rawCommand}\n`))
|
|
119
|
+
if (command !== 'create') {
|
|
120
|
+
console.error(chalk.red(`\n ❌ Unknown command: ${rawCommand}\n`))
|
|
118
121
|
printHelp()
|
|
119
122
|
process.exit(1)
|
|
120
123
|
}
|
|
121
124
|
|
|
122
|
-
const docName = outputName || command
|
|
123
|
-
const prompt = entry.fn(projectInfo)
|
|
124
|
-
|
|
125
|
-
console.log(chalk.bold(`\n📝 Generating: ${entry.label}`))
|
|
126
|
-
console.log(chalk.dim(` Output: ./output/${docName}.{md,txt,html}\n`))
|
|
127
|
-
|
|
128
125
|
try {
|
|
129
|
-
await
|
|
126
|
+
await runCreate()
|
|
130
127
|
} catch (err) {
|
|
131
128
|
if (err.status === 401) {
|
|
132
|
-
console.error(chalk.red('\n❌ Authentication failed.
|
|
129
|
+
console.error(chalk.red('\n ❌ Authentication failed. Run "handover key <your-api-key>" to set your API key.\n'))
|
|
133
130
|
} else {
|
|
134
|
-
console.error(chalk.red(`\n❌ Error: ${err.message}\n`))
|
|
131
|
+
console.error(chalk.red(`\n ❌ Error: ${err.message}\n`))
|
|
135
132
|
}
|
|
136
133
|
process.exit(1)
|
|
137
134
|
}
|