client-handover 1.2.1 → 1.4.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/cli.js +43 -54
- package/generator.js +8 -52
- package/package.json +2 -2
package/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { generateDoc
|
|
3
|
+
import { generateDoc } from './generator.js'
|
|
4
4
|
import { technicalHandoverPrompt, nonTechnicalHandoverPrompt } from './prompts.js'
|
|
5
5
|
import { scanProject } from './scanner.js'
|
|
6
6
|
import chalk from 'chalk'
|
|
@@ -31,8 +31,7 @@ function printBanner() {
|
|
|
31
31
|
function printHelp() {
|
|
32
32
|
printBanner()
|
|
33
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')
|
|
34
|
+
console.log(' handover /create Generate technical & non-technical handover documents\n')
|
|
36
35
|
console.log(chalk.dim(' Example:'))
|
|
37
36
|
console.log(' cd my-client-project')
|
|
38
37
|
console.log(' handover /create\n')
|
|
@@ -52,10 +51,9 @@ async function runSetup() {
|
|
|
52
51
|
if (fs.existsSync(configPath)) config = JSON.parse(fs.readFileSync(configPath, 'utf-8'))
|
|
53
52
|
} catch {}
|
|
54
53
|
|
|
55
|
-
const hasApiKey = !!resolveApiKey(config)
|
|
56
54
|
const hasDeveloperInfo = !!config.developerName
|
|
57
55
|
|
|
58
|
-
if (
|
|
56
|
+
if (hasDeveloperInfo) {
|
|
59
57
|
return {
|
|
60
58
|
name: config.developerName,
|
|
61
59
|
company: config.developerCompany || '',
|
|
@@ -70,30 +68,18 @@ async function runSetup() {
|
|
|
70
68
|
|
|
71
69
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
|
|
72
70
|
|
|
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
|
-
const key = await ask(rl, ' Enter your Anthropic API key (or press Enter to skip): ')
|
|
77
|
-
if (key) {
|
|
78
|
-
config.apiKey = key
|
|
79
|
-
console.log(chalk.green(' ✓ API key saved.\n'))
|
|
80
|
-
} else {
|
|
81
|
-
console.log(chalk.dim(' Skipped. Run "handover key <your-api-key>" at any time.\n'))
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
71
|
if (!hasDeveloperInfo) {
|
|
86
72
|
console.log(' Your details will appear in all generated handover documents.\n')
|
|
87
|
-
const name = await ask(rl, '
|
|
73
|
+
const name = await ask(rl, ' Developer name: ')
|
|
88
74
|
if (name) config.developerName = name
|
|
89
75
|
|
|
90
|
-
const company = await ask(rl, '
|
|
76
|
+
const company = await ask(rl, ' Company name (press Enter if not applicable): ')
|
|
91
77
|
if (company) config.developerCompany = company
|
|
92
78
|
|
|
93
|
-
const email = await ask(rl, '
|
|
79
|
+
const email = await ask(rl, ' Email address: ')
|
|
94
80
|
if (email) config.developerEmail = email
|
|
95
81
|
|
|
96
|
-
const phone = await ask(rl, '
|
|
82
|
+
const phone = await ask(rl, ' Phone number (optional, press Enter to skip): ')
|
|
97
83
|
if (phone) config.developerPhone = phone
|
|
98
84
|
|
|
99
85
|
console.log()
|
|
@@ -117,31 +103,48 @@ async function runCreate() {
|
|
|
117
103
|
printBanner()
|
|
118
104
|
const developerInfo = await runSetup()
|
|
119
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
|
+
|
|
120
115
|
console.log(chalk.dim(' Scanning project files...'))
|
|
121
116
|
const projectInfo = scanProject(process.cwd())
|
|
122
117
|
|
|
123
118
|
const techDir = './technical-handover'
|
|
124
|
-
const clientDir = './handover'
|
|
119
|
+
const clientDir = './client-handover'
|
|
125
120
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
+
}
|
|
129
126
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
+
}
|
|
133
132
|
|
|
134
133
|
console.log(chalk.green.bold('\n ✅ Handover documents created successfully!\n'))
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
+
}
|
|
145
148
|
}
|
|
146
149
|
|
|
147
150
|
async function main() {
|
|
@@ -154,16 +157,6 @@ async function main() {
|
|
|
154
157
|
|
|
155
158
|
const command = normalizeCommand(rawCommand)
|
|
156
159
|
|
|
157
|
-
if (command === 'key') {
|
|
158
|
-
if (!secondArg) {
|
|
159
|
-
console.error(chalk.red('\n ❌ Usage: handover key <your-api-key>\n'))
|
|
160
|
-
process.exit(1)
|
|
161
|
-
}
|
|
162
|
-
saveApiKey(secondArg)
|
|
163
|
-
console.log(chalk.green('\n ✅ API key saved. Run "handover /create" to get started.\n'))
|
|
164
|
-
process.exit(0)
|
|
165
|
-
}
|
|
166
|
-
|
|
167
160
|
if (command !== 'create') {
|
|
168
161
|
console.error(chalk.red(`\n ❌ Unknown command: ${rawCommand}\n`))
|
|
169
162
|
printHelp()
|
|
@@ -173,11 +166,7 @@ async function main() {
|
|
|
173
166
|
try {
|
|
174
167
|
await runCreate()
|
|
175
168
|
} catch (err) {
|
|
176
|
-
|
|
177
|
-
console.error(chalk.red('\n ❌ Authentication failed. Run "handover key <your-api-key>" to set your API key.\n'))
|
|
178
|
-
} else {
|
|
179
|
-
console.error(chalk.red(`\n ❌ Error: ${err.message}\n`))
|
|
180
|
-
}
|
|
169
|
+
console.error(chalk.red(`\n ❌ Error: ${err.message}\n`))
|
|
181
170
|
process.exit(1)
|
|
182
171
|
}
|
|
183
172
|
}
|
package/generator.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { query } from '@anthropic-ai/claude-code'
|
|
2
2
|
import {
|
|
3
3
|
Document, Packer, Paragraph, TextRun, HeadingLevel,
|
|
4
4
|
AlignmentType, BorderStyle, TableRow, TableCell, Table,
|
|
@@ -6,51 +6,6 @@ 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 })
|
|
54
9
|
|
|
55
10
|
// ---------------------------------------------------------------------------
|
|
56
11
|
// Inline text parser — splits a line into TextRun segments handling **bold**,
|
|
@@ -298,13 +253,14 @@ function buildDocx(markdown) {
|
|
|
298
253
|
export async function generateDoc(prompt, outputName = 'handover', outputDir = './output') {
|
|
299
254
|
console.log('⏳ Generating document with Claude...\n')
|
|
300
255
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
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
|
+
}
|
|
306
262
|
|
|
307
|
-
|
|
263
|
+
if (!markdown) throw new Error('No response received from Claude')
|
|
308
264
|
|
|
309
265
|
if (!fs.existsSync(outputDir)) {
|
|
310
266
|
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.4.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/claude-code": "latest",
|
|
33
33
|
"chalk": "^5.3.0",
|
|
34
34
|
"docx": "^8.5.0"
|
|
35
35
|
}
|