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.
Files changed (3) hide show
  1. package/cli.js +43 -54
  2. package/generator.js +8 -52
  3. 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, resolveApiKey, saveApiKey } from './generator.js'
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 (hasApiKey && hasDeveloperInfo) {
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, ' Your full name: ')
73
+ const name = await ask(rl, ' Developer name: ')
88
74
  if (name) config.developerName = name
89
75
 
90
- const company = await ask(rl, ' Your company name (press Enter if not applicable): ')
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, ' Your email address: ')
79
+ const email = await ask(rl, ' Email address: ')
94
80
  if (email) config.developerEmail = email
95
81
 
96
- const phone = await ask(rl, ' Your phone number (optional, press Enter to skip): ')
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
- console.log(chalk.bold('\n Generating Technical Handover Document...'))
127
- const techPrompt = technicalHandoverPrompt(projectInfo, developerInfo)
128
- await generateDoc(techPrompt, 'technical-handover', techDir)
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
- console.log(chalk.bold('\n Generating Client Handover Document...'))
131
- const nonTechPrompt = nonTechnicalHandoverPrompt(projectInfo, developerInfo)
132
- await generateDoc(nonTechPrompt, 'handover', clientDir)
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
- 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
- console.log(` ${chalk.cyan('./handover/')}`)
141
- console.log(` handover.md`)
142
- console.log(` handover.txt`)
143
- console.log(` handover.docx`)
144
- console.log()
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
- if (err.status === 401) {
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 Anthropic from '@anthropic-ai/sdk'
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
- const message = await client.messages.create({
302
- model: 'claude-sonnet-4-20250514',
303
- max_tokens: 4096,
304
- messages: [{ role: 'user', content: prompt }]
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
- const markdown = message.content[0].text
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.2.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/sdk": "^0.39.0",
32
+ "@anthropic-ai/claude-code": "latest",
33
33
  "chalk": "^5.3.0",
34
34
  "docx": "^8.5.0"
35
35
  }