client-handover 1.3.0 → 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 +4 -32
- package/generator.js +8 -52
- package/package.json +2 -2
package/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
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,18 +68,6 @@ 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
|
-
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
71
|
if (!hasDeveloperInfo) {
|
|
86
72
|
console.log(' Your details will appear in all generated handover documents.\n')
|
|
87
73
|
const name = await ask(rl, ' Developer name: ')
|
|
@@ -171,16 +157,6 @@ async function main() {
|
|
|
171
157
|
|
|
172
158
|
const command = normalizeCommand(rawCommand)
|
|
173
159
|
|
|
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
160
|
if (command !== 'create') {
|
|
185
161
|
console.error(chalk.red(`\n ❌ Unknown command: ${rawCommand}\n`))
|
|
186
162
|
printHelp()
|
|
@@ -190,11 +166,7 @@ async function main() {
|
|
|
190
166
|
try {
|
|
191
167
|
await runCreate()
|
|
192
168
|
} catch (err) {
|
|
193
|
-
|
|
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
|
-
}
|
|
169
|
+
console.error(chalk.red(`\n ❌ Error: ${err.message}\n`))
|
|
198
170
|
process.exit(1)
|
|
199
171
|
}
|
|
200
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
|
}
|