client-handover 1.0.6 → 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 CHANGED
@@ -200,6 +200,12 @@ 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
+
203
209
  ### 1.0.6
204
210
  - Auto-scans your actual project files on every run — reads package.json, config files, env variable keys, deploy configs, folder structure, and more
205
211
  - Generated documents are now tailored to your real project, not generic templates
package/cli.js CHANGED
@@ -1,73 +1,103 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { setup } from './setup.js'
4
- import { deploy } from './deploy.js'
5
- import { credentials } from './credentials.js'
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'
9
5
  import { scanProject } from './scanner.js'
10
6
  import chalk from 'chalk'
7
+ import readline from 'readline'
11
8
  import fs from 'fs'
12
9
  import path from 'path'
13
-
14
- const COMMANDS = {
15
- 'setup': { fn: setup, label: 'Project Setup & Dependencies' },
16
- 'deploy': { fn: deploy, label: 'Deployment & Hosting' },
17
- 'credentials': { fn: credentials, label: 'Credentials & Access' },
18
- 'handover': { fn: handover, label: 'Full Handover Document' },
19
- 'license': { fn: license, label: 'Licensing & Attribution' },
20
- }
10
+ import os from 'os'
21
11
 
22
12
  function normalizeCommand(cmd) {
23
- // Git Bash converts /handover → C:\Program Files\Git\handover before Node sees it
24
- // path.basename extracts just the last segment regardless of what form it arrives in
25
13
  return path.basename(cmd).replace(/^\/+/, '')
26
14
  }
27
15
 
28
16
  function printHelp() {
29
- console.log(chalk.bold('\n🚀 handover-cliFrontend Website Handover - by Scott AK (sabrkei)\n'))
17
+ console.log(chalk.bold('\n handover — Website Handover Document Generator\n'))
30
18
  console.log(chalk.dim('Usage:'))
31
- console.log(' handover <command> [project-info-file] [output-name]\n')
32
- console.log(chalk.dim('Commands:'))
33
- Object.entries(COMMANDS).forEach(([cmd, { label }]) => {
34
- console.log(` ${chalk.cyan(cmd.padEnd(16))} ${label}`)
35
- })
36
- console.log(` ${chalk.cyan('all'.padEnd(16))} All sections in a single folder`)
37
- console.log(` ${chalk.cyan('key <api-key>'.padEnd(16))} Save your Anthropic API key (one-time setup)`)
38
- console.log('\n' + chalk.dim('Examples:'))
39
- console.log(' handover handover # Full doc with placeholders')
40
- console.log(' handover setup project-info.txt # Setup section using your notes')
41
- console.log(' handover handover project-info.txt my-client # Full doc, custom filename')
42
- console.log(' handover all project-info.txt acme-client # Every section in output/acme-client/')
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')
43
64
  console.log()
65
+
66
+ return { name: name || '', company: company || '', email: email || '', phone: phone || '' }
44
67
  }
45
68
 
46
- async function runAll(projectInfo, folderName, outputName) {
47
- const sections = [
48
- { key: 'handover', fn: handover, label: 'Full Handover Document' },
49
- { key: 'setup', fn: setup, label: 'Project Setup & Dependencies' },
50
- { key: 'deploy', fn: deploy, label: 'Deployment & Hosting' },
51
- { key: 'credentials', fn: credentials, label: 'Credentials & Access' },
52
- { key: 'license', fn: license, label: 'Licensing & Attribution' },
53
- ]
69
+ async function runCreate() {
70
+ const developerInfo = await ensureDeveloperInfo()
54
71
 
55
- const outputDir = `./output/${folderName}`
72
+ console.log(chalk.dim(' Scanning project files...'))
73
+ const projectInfo = scanProject(process.cwd())
56
74
 
57
- console.log(chalk.bold(`\n📁 Generating all sections into: ${outputDir}\n`))
75
+ const techDir = './technical-handover'
76
+ const clientDir = './handover'
58
77
 
59
- for (const section of sections) {
60
- console.log(chalk.bold(`📝 Generating: ${section.label}`))
61
- const prompt = section.fn(projectInfo)
62
- await generateDoc(prompt, section.key, outputDir)
63
- console.log()
64
- }
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)
65
85
 
66
- console.log(chalk.green.bold(`✅ All sections saved to ${outputDir}\n`))
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()
67
97
  }
68
98
 
69
99
  async function main() {
70
- const [,, rawCommand, infoFile, outputName] = process.argv
100
+ const [,, rawCommand, secondArg] = process.argv
71
101
 
72
102
  if (!rawCommand || rawCommand === '--help' || rawCommand === '-h') {
73
103
  printHelp()
@@ -77,68 +107,28 @@ async function main() {
77
107
  const command = normalizeCommand(rawCommand)
78
108
 
79
109
  if (command === 'key') {
80
- const key = infoFile // second arg is the key
81
- if (!key) {
82
- 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'))
83
112
  process.exit(1)
84
113
  }
85
- saveApiKey(key)
86
- console.log(chalk.green('\n✅ API key saved. You\'re all set run any handover command.\n'))
114
+ saveApiKey(secondArg)
115
+ console.log(chalk.green('\n ✅ API key saved. Run "handover /create" to get started.\n'))
87
116
  process.exit(0)
88
117
  }
89
118
 
90
- // Scan the current project directory
91
- console.log(chalk.dim('\n🔍 Scanning project...'))
92
- const scannedContext = scanProject(process.cwd())
93
-
94
- // Read optional extra project info file
95
- let extraInfo = ''
96
- if (infoFile) {
97
- if (!fs.existsSync(infoFile)) {
98
- console.error(chalk.red(`\n❌ File not found: ${infoFile}\n`))
99
- process.exit(1)
100
- }
101
- extraInfo = fs.readFileSync(infoFile, 'utf-8')
102
- console.log(chalk.green(`📄 Loaded extra info from: ${infoFile}`))
103
- }
104
-
105
- const projectInfo = [scannedContext, extraInfo].filter(Boolean).join('\n\n---\n\n')
106
-
107
- if (command === 'all') {
108
- const folderName = outputName || 'all'
109
- try {
110
- await runAll(projectInfo, folderName)
111
- } catch (err) {
112
- if (err.status === 401) {
113
- console.error(chalk.red('\n❌ Authentication failed. Set ANTHROPIC_API_KEY or install Claude Code.\n'))
114
- } else {
115
- console.error(chalk.red(`\n❌ Error: ${err.message}\n`))
116
- }
117
- process.exit(1)
118
- }
119
- return
120
- }
121
-
122
- const entry = COMMANDS[command]
123
- if (!entry) {
124
- console.error(chalk.red(`\n❌ Unknown command: ${rawCommand}\n`))
119
+ if (command !== 'create') {
120
+ console.error(chalk.red(`\n Unknown command: ${rawCommand}\n`))
125
121
  printHelp()
126
122
  process.exit(1)
127
123
  }
128
124
 
129
- const docName = outputName || command
130
- const prompt = entry.fn(projectInfo)
131
-
132
- console.log(chalk.bold(`\n📝 Generating: ${entry.label}`))
133
- console.log(chalk.dim(` Output: ./output/${docName}.{md,txt,html}\n`))
134
-
135
125
  try {
136
- await generateDoc(prompt, docName, './output')
126
+ await runCreate()
137
127
  } catch (err) {
138
128
  if (err.status === 401) {
139
- console.error(chalk.red('\n❌ Authentication failed. Set ANTHROPIC_API_KEY or install Claude Code.\n'))
129
+ console.error(chalk.red('\n ❌ Authentication failed. Run "handover key <your-api-key>" to set your API key.\n'))
140
130
  } else {
141
- console.error(chalk.red(`\n❌ Error: ${err.message}\n`))
131
+ console.error(chalk.red(`\n ❌ Error: ${err.message}\n`))
142
132
  }
143
133
  process.exit(1)
144
134
  }
package/generator.js CHANGED
@@ -1,5 +1,9 @@
1
1
  import Anthropic from '@anthropic-ai/sdk'
2
- import { marked } from 'marked'
2
+ import {
3
+ Document, Packer, Paragraph, TextRun, HeadingLevel,
4
+ AlignmentType, BorderStyle, TableRow, TableCell, Table,
5
+ WidthType, ShadingType
6
+ } from 'docx'
3
7
  import fs from 'fs'
4
8
  import path from 'path'
5
9
  import os from 'os'
@@ -7,15 +11,12 @@ import os from 'os'
7
11
  function resolveApiKey() {
8
12
  if (process.env.ANTHROPIC_API_KEY) return process.env.ANTHROPIC_API_KEY
9
13
 
10
- // Check saved key from `handover key <your-key>`
11
14
  const configPath = path.join(os.homedir(), '.handover', 'config.json')
12
15
  if (fs.existsSync(configPath)) {
13
16
  try {
14
17
  const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'))
15
18
  if (config?.apiKey) return config.apiKey
16
- } catch {
17
- // ignore malformed config file
18
- }
19
+ } catch {}
19
20
  }
20
21
 
21
22
  const credentialsPath = path.join(os.homedir(), '.claude', '.credentials.json')
@@ -24,42 +25,293 @@ function resolveApiKey() {
24
25
  const creds = JSON.parse(fs.readFileSync(credentialsPath, 'utf-8'))
25
26
  const token = creds?.claudeAiOauth?.accessToken
26
27
  const expiresAt = creds?.claudeAiOauth?.expiresAt
27
- if (token && (!expiresAt || expiresAt > Date.now())) {
28
- return token
29
- }
30
- } catch {
31
- // ignore malformed credentials file
32
- }
28
+ if (token && (!expiresAt || expiresAt > Date.now())) return token
29
+ } catch {}
33
30
  }
34
31
 
35
32
  return null
36
33
  }
37
34
 
35
+ export function loadDeveloperInfo() {
36
+ const configPath = path.join(os.homedir(), '.handover', 'config.json')
37
+ try {
38
+ if (fs.existsSync(configPath)) {
39
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'))
40
+ return {
41
+ name: config.developerName || '',
42
+ company: config.developerCompany || '',
43
+ email: config.developerEmail || '',
44
+ phone: config.developerPhone || '',
45
+ }
46
+ }
47
+ } catch {}
48
+ return { name: '', company: '', email: '', phone: '' }
49
+ }
50
+
38
51
  export function saveApiKey(key) {
39
52
  const configDir = path.join(os.homedir(), '.handover')
40
53
  const configPath = path.join(configDir, 'config.json')
41
54
  if (!fs.existsSync(configDir)) fs.mkdirSync(configDir, { recursive: true })
42
- fs.writeFileSync(configPath, JSON.stringify({ apiKey: key }, null, 2), 'utf-8')
55
+ let config = {}
56
+ try { config = JSON.parse(fs.readFileSync(configPath, 'utf-8')) } catch {}
57
+ config.apiKey = key
58
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8')
43
59
  }
44
60
 
45
61
  const apiKey = resolveApiKey()
46
62
  if (!apiKey) {
47
- console.error('\n❌ No API key found. Set ANTHROPIC_API_KEY or install Claude Code (code.visualstudio.com/download).\n')
63
+ console.error('\n❌ No API key found. Run "handover key <your-api-key>" or install Claude Code.\n')
48
64
  process.exit(1)
49
65
  }
50
66
 
51
67
  const client = new Anthropic({ apiKey })
52
68
 
53
- /**
54
- * Generate a handover document using the Claude API
55
- * @param {string} prompt - The command prompt to send
56
- * @param {string} outputName - Base filename (without extension)
57
- * @param {string} outputDir - Directory to save files
58
- */
69
+ // ---------------------------------------------------------------------------
70
+ // Inline text parser — splits a line into TextRun segments handling **bold**,
71
+ // *italic*, `code`, and [link text](url) just the text
72
+ // ---------------------------------------------------------------------------
73
+ function parseInline(text) {
74
+ const runs = []
75
+ // Strip markdown links to plain text
76
+ text = text.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')
77
+
78
+ const pattern = /(\*\*(.+?)\*\*|\*(.+?)\*|`(.+?)`)/g
79
+ let lastIndex = 0
80
+ let match
81
+
82
+ while ((match = pattern.exec(text)) !== null) {
83
+ if (match.index > lastIndex) {
84
+ runs.push(new TextRun({ text: text.slice(lastIndex, match.index) }))
85
+ }
86
+ if (match[2] !== undefined) {
87
+ runs.push(new TextRun({ text: match[2], bold: true }))
88
+ } else if (match[3] !== undefined) {
89
+ runs.push(new TextRun({ text: match[3], italics: true }))
90
+ } else if (match[4] !== undefined) {
91
+ runs.push(new TextRun({
92
+ text: match[4],
93
+ font: 'Courier New',
94
+ size: 18,
95
+ shading: { type: ShadingType.CLEAR, fill: 'F4F4F4' },
96
+ }))
97
+ }
98
+ lastIndex = match.index + match[0].length
99
+ }
100
+
101
+ if (lastIndex < text.length) {
102
+ runs.push(new TextRun({ text: text.slice(lastIndex) }))
103
+ }
104
+
105
+ return runs.length ? runs : [new TextRun({ text })]
106
+ }
107
+
108
+ // ---------------------------------------------------------------------------
109
+ // Convert markdown string to an array of docx Paragraph/Table objects
110
+ // ---------------------------------------------------------------------------
111
+ function markdownToDocxChildren(markdown) {
112
+ const children = []
113
+ const lines = markdown.split('\n')
114
+ let i = 0
115
+
116
+ while (i < lines.length) {
117
+ const line = lines[i]
118
+
119
+ // Fenced code block
120
+ if (line.trim().startsWith('```')) {
121
+ const codeLines = []
122
+ i++
123
+ while (i < lines.length && !lines[i].trim().startsWith('```')) {
124
+ codeLines.push(lines[i])
125
+ i++
126
+ }
127
+ for (const codeLine of codeLines) {
128
+ children.push(new Paragraph({
129
+ children: [new TextRun({ text: codeLine || ' ', font: 'Courier New', size: 18 })],
130
+ shading: { type: ShadingType.CLEAR, fill: 'F4F4F4' },
131
+ spacing: { before: 0, after: 0 },
132
+ indent: { left: 360 },
133
+ }))
134
+ }
135
+ // Add small gap after code block
136
+ children.push(new Paragraph({ text: '' }))
137
+ i++
138
+ continue
139
+ }
140
+
141
+ // Horizontal rule
142
+ if (/^---+$/.test(line.trim())) {
143
+ children.push(new Paragraph({
144
+ text: '',
145
+ border: { bottom: { style: BorderStyle.SINGLE, size: 6, color: 'CCCCCC' } },
146
+ spacing: { before: 200, after: 200 },
147
+ }))
148
+ i++
149
+ continue
150
+ }
151
+
152
+ // Headings
153
+ const h1 = line.match(/^# (.+)/)
154
+ const h2 = line.match(/^## (.+)/)
155
+ const h3 = line.match(/^### (.+)/)
156
+ const h4 = line.match(/^#{4,6} (.+)/)
157
+
158
+ if (h1) {
159
+ children.push(new Paragraph({
160
+ children: parseInline(h1[1]),
161
+ heading: HeadingLevel.HEADING_1,
162
+ spacing: { before: 400, after: 160 },
163
+ }))
164
+ i++; continue
165
+ }
166
+ if (h2) {
167
+ children.push(new Paragraph({
168
+ children: parseInline(h2[1]),
169
+ heading: HeadingLevel.HEADING_2,
170
+ spacing: { before: 320, after: 120 },
171
+ }))
172
+ i++; continue
173
+ }
174
+ if (h3) {
175
+ children.push(new Paragraph({
176
+ children: parseInline(h3[1]),
177
+ heading: HeadingLevel.HEADING_3,
178
+ spacing: { before: 240, after: 80 },
179
+ }))
180
+ i++; continue
181
+ }
182
+ if (h4) {
183
+ children.push(new Paragraph({
184
+ children: [new TextRun({ text: h4[1], bold: true })],
185
+ spacing: { before: 160, after: 80 },
186
+ }))
187
+ i++; continue
188
+ }
189
+
190
+ // Checkbox list items - [ ] or - [x]
191
+ const checkbox = line.match(/^(\s*)- \[(x| )\] (.+)/i)
192
+ if (checkbox) {
193
+ const checked = checkbox[2].toLowerCase() === 'x'
194
+ children.push(new Paragraph({
195
+ children: [
196
+ new TextRun({ text: checked ? '☑ ' : '☐ ' }),
197
+ ...parseInline(checkbox[3]),
198
+ ],
199
+ indent: { left: 360 },
200
+ spacing: { before: 40, after: 40 },
201
+ }))
202
+ i++; continue
203
+ }
204
+
205
+ // Bullet list items - or *
206
+ const bullet = line.match(/^(\s*)[-*+] (.+)/)
207
+ if (bullet) {
208
+ const indent = bullet[1].length > 0 ? 720 : 360
209
+ children.push(new Paragraph({
210
+ children: [new TextRun({ text: '• ' }), ...parseInline(bullet[2])],
211
+ indent: { left: indent },
212
+ spacing: { before: 40, after: 40 },
213
+ }))
214
+ i++; continue
215
+ }
216
+
217
+ // Numbered list items
218
+ const numbered = line.match(/^\d+\. (.+)/)
219
+ if (numbered) {
220
+ children.push(new Paragraph({
221
+ children: parseInline(numbered[1]),
222
+ numbering: { reference: 'default-numbering', level: 0 },
223
+ indent: { left: 360 },
224
+ spacing: { before: 40, after: 40 },
225
+ }))
226
+ i++; continue
227
+ }
228
+
229
+ // Bold-only line (often used as a label/key line like **Key:** value)
230
+ // Handled by parseInline already — fall through to normal paragraph
231
+
232
+ // Empty line
233
+ if (line.trim() === '') {
234
+ children.push(new Paragraph({ text: '', spacing: { before: 0, after: 80 } }))
235
+ i++; continue
236
+ }
237
+
238
+ // Normal paragraph
239
+ children.push(new Paragraph({
240
+ children: parseInline(line),
241
+ spacing: { before: 0, after: 80 },
242
+ }))
243
+ i++
244
+ }
245
+
246
+ return children
247
+ }
248
+
249
+ // ---------------------------------------------------------------------------
250
+ // Build a docx Document from markdown
251
+ // ---------------------------------------------------------------------------
252
+ function buildDocx(markdown) {
253
+ const children = markdownToDocxChildren(markdown)
254
+
255
+ return new Document({
256
+ numbering: {
257
+ config: [{
258
+ reference: 'default-numbering',
259
+ levels: [{
260
+ level: 0,
261
+ format: 'decimal',
262
+ text: '%1.',
263
+ alignment: AlignmentType.LEFT,
264
+ style: { paragraph: { indent: { left: 360, hanging: 260 } } },
265
+ }],
266
+ }],
267
+ },
268
+ styles: {
269
+ default: {
270
+ document: {
271
+ run: { font: 'Calibri', size: 22 },
272
+ paragraph: { spacing: { line: 276 } },
273
+ },
274
+ },
275
+ paragraphStyles: [
276
+ {
277
+ id: 'Heading1',
278
+ name: 'Heading 1',
279
+ basedOn: 'Normal',
280
+ next: 'Normal',
281
+ run: { bold: true, size: 36, color: '1a1a1a' },
282
+ paragraph: { spacing: { before: 400, after: 160 } },
283
+ },
284
+ {
285
+ id: 'Heading2',
286
+ name: 'Heading 2',
287
+ basedOn: 'Normal',
288
+ next: 'Normal',
289
+ run: { bold: true, size: 28, color: '2c2c2c' },
290
+ paragraph: {
291
+ spacing: { before: 320, after: 120 },
292
+ border: { bottom: { style: BorderStyle.SINGLE, size: 4, color: 'E0E0E0' } },
293
+ },
294
+ },
295
+ {
296
+ id: 'Heading3',
297
+ name: 'Heading 3',
298
+ basedOn: 'Normal',
299
+ next: 'Normal',
300
+ run: { bold: true, size: 24, color: '444444' },
301
+ paragraph: { spacing: { before: 240, after: 80 } },
302
+ },
303
+ ],
304
+ },
305
+ sections: [{ properties: {}, children }],
306
+ })
307
+ }
308
+
309
+ // ---------------------------------------------------------------------------
310
+ // Main export
311
+ // ---------------------------------------------------------------------------
59
312
  export async function generateDoc(prompt, outputName = 'handover', outputDir = './output') {
60
313
  console.log('⏳ Generating document with Claude...\n')
61
314
 
62
- // Call Claude API
63
315
  const message = await client.messages.create({
64
316
  model: 'claude-sonnet-4-20250514',
65
317
  max_tokens: 4096,
@@ -68,105 +320,38 @@ export async function generateDoc(prompt, outputName = 'handover', outputDir = '
68
320
 
69
321
  const markdown = message.content[0].text
70
322
 
71
- // Ensure output directory exists
72
323
  if (!fs.existsSync(outputDir)) {
73
324
  fs.mkdirSync(outputDir, { recursive: true })
74
325
  }
75
326
 
76
327
  const basePath = path.join(outputDir, outputName)
77
328
 
78
- // --- 1. Save as Markdown ---
329
+ // Save as Markdown
79
330
  const mdPath = `${basePath}.md`
80
331
  fs.writeFileSync(mdPath, markdown, 'utf-8')
81
- console.log(`✅ Markdown saved: ${mdPath}`)
332
+ console.log(`✅ Markdown saved: ${mdPath}`)
82
333
 
83
- // --- 2. Save as Plain Text ---
334
+ // Save as plain text
84
335
  const plainText = markdown
85
- .replace(/#{1,6}\s+/g, '') // Remove headings
86
- .replace(/\*\*(.*?)\*\*/g, '$1') // Remove bold
87
- .replace(/\*(.*?)\*/g, '$1') // Remove italic
88
- .replace(/`{1,3}[^`]*`{1,3}/g, (m) => m.replace(/`/g, '')) // Remove code ticks
89
- .replace(/\[([^\]]+)\]\([^)]+\)/g, '$1') // Remove links, keep text
90
- .replace(/[-*+] /g, '• ') // Convert bullets
91
- .replace(/\n{3,}/g, '\n\n') // Clean extra blank lines
336
+ .replace(/#{1,6}\s+/g, '')
337
+ .replace(/\*\*(.*?)\*\*/g, '$1')
338
+ .replace(/\*(.*?)\*/g, '$1')
339
+ .replace(/`{1,3}[^`]*`{1,3}/g, m => m.replace(/`/g, ''))
340
+ .replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')
341
+ .replace(/[-*+] /g, '• ')
342
+ .replace(/\n{3,}/g, '\n\n')
92
343
  .trim()
93
344
 
94
345
  const txtPath = `${basePath}.txt`
95
346
  fs.writeFileSync(txtPath, plainText, 'utf-8')
96
347
  console.log(`✅ Plain text saved: ${txtPath}`)
97
348
 
98
- // --- 3. Save as HTML ---
99
- const htmlBody = marked(markdown)
100
- const html = `<!DOCTYPE html>
101
- <html lang="en">
102
- <head>
103
- <meta charset="UTF-8" />
104
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
105
- <title>Handover Document</title>
106
- <style>
107
- body {
108
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
109
- max-width: 860px;
110
- margin: 0 auto;
111
- padding: 2rem;
112
- color: #1a1a1a;
113
- line-height: 1.7;
114
- }
115
- h1 { border-bottom: 2px solid #e0e0e0; padding-bottom: 0.5rem; }
116
- h2 { margin-top: 2.5rem; color: #2c2c2c; }
117
- h3 { color: #444; }
118
- code {
119
- background: #f4f4f4;
120
- padding: 0.2em 0.4em;
121
- border-radius: 4px;
122
- font-size: 0.9em;
123
- }
124
- pre {
125
- background: #f4f4f4;
126
- padding: 1rem;
127
- border-radius: 6px;
128
- overflow-x: auto;
129
- }
130
- pre code { background: none; padding: 0; }
131
- table {
132
- width: 100%;
133
- border-collapse: collapse;
134
- margin: 1rem 0;
135
- }
136
- th, td {
137
- border: 1px solid #ddd;
138
- padding: 0.6rem 0.8rem;
139
- text-align: left;
140
- }
141
- th { background: #f0f0f0; font-weight: 600; }
142
- blockquote {
143
- border-left: 4px solid #ccc;
144
- margin: 0;
145
- padding-left: 1rem;
146
- color: #555;
147
- }
148
- .badge {
149
- display: inline-block;
150
- background: #e8f4fd;
151
- color: #0969da;
152
- padding: 0.2em 0.6em;
153
- border-radius: 4px;
154
- font-size: 0.8em;
155
- font-weight: 600;
156
- }
157
- input[type="checkbox"] { margin-right: 0.4rem; }
158
- </style>
159
- </head>
160
- <body>
161
- ${htmlBody}
162
- </body>
163
- </html>`
164
-
165
- const htmlPath = `${basePath}.html`
166
- fs.writeFileSync(htmlPath, html, 'utf-8')
167
- console.log(`✅ HTML saved: ${htmlPath}`)
168
-
169
- console.log('\n🎉 All formats generated successfully!\n')
349
+ // Save as Word document
350
+ const doc = buildDocx(markdown)
351
+ const docxBuffer = await Packer.toBuffer(doc)
352
+ const docxPath = `${basePath}.docx`
353
+ fs.writeFileSync(docxPath, docxBuffer)
354
+ console.log(`✅ Word doc saved: ${docxPath}`)
170
355
 
171
- return { markdown, plainText, html }
356
+ return { markdown, plainText }
172
357
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "client-handover",
3
- "version": "1.0.6",
3
+ "version": "1.1.0",
4
4
  "description": "AI-powered handover document generator for frontend developers handing off client websites",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -11,13 +11,9 @@
11
11
  "cli.js",
12
12
  "index.js",
13
13
  "generator.js",
14
- "handover.js",
15
- "setup.js",
16
- "deploy.js",
17
- "credentials.js",
18
- "license.js",
19
- "postinstall.js",
14
+ "prompts.js",
20
15
  "scanner.js",
16
+ "postinstall.js",
21
17
  "README.md"
22
18
  ],
23
19
  "scripts": {
@@ -41,7 +37,7 @@
41
37
  "dependencies": {
42
38
  "@anthropic-ai/sdk": "^0.39.0",
43
39
  "chalk": "^5.3.0",
44
- "client-handover": "^1.0.2",
45
- "marked": "^12.0.0"
40
+ "client-handover": "^1.0.6",
41
+ "docx": "^8.5.0"
46
42
  }
47
43
  }
package/postinstall.js CHANGED
@@ -9,56 +9,87 @@ const configDir = path.join(os.homedir(), '.handover')
9
9
  const configPath = path.join(configDir, 'config.json')
10
10
  const claudeCredsPath = path.join(os.homedir(), '.claude', '.credentials.json')
11
11
 
12
- function alreadyConfigured() {
13
- if (process.env.ANTHROPIC_API_KEY) return true
12
+ function loadConfig() {
13
+ try {
14
+ if (fs.existsSync(configPath)) {
15
+ return JSON.parse(fs.readFileSync(configPath, 'utf-8'))
16
+ }
17
+ } catch {}
18
+ return {}
19
+ }
14
20
 
15
- if (fs.existsSync(configPath)) {
16
- try {
17
- const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'))
18
- if (config?.apiKey) return true
19
- } catch {}
20
- }
21
+ function saveConfig(config) {
22
+ if (!fs.existsSync(configDir)) fs.mkdirSync(configDir, { recursive: true })
23
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8')
24
+ }
21
25
 
22
- if (fs.existsSync(claudeCredsPath)) {
23
- try {
26
+ function hasApiKey(config) {
27
+ if (process.env.ANTHROPIC_API_KEY) return true
28
+ if (config?.apiKey) return true
29
+ try {
30
+ if (fs.existsSync(claudeCredsPath)) {
24
31
  const creds = JSON.parse(fs.readFileSync(claudeCredsPath, 'utf-8'))
25
32
  const token = creds?.claudeAiOauth?.accessToken
26
33
  const expiresAt = creds?.claudeAiOauth?.expiresAt
27
34
  if (token && (!expiresAt || expiresAt > Date.now())) return true
28
- } catch {}
29
- }
30
-
35
+ }
36
+ } catch {}
31
37
  return false
32
38
  }
33
39
 
34
- function saveKey(key) {
35
- if (!fs.existsSync(configDir)) fs.mkdirSync(configDir, { recursive: true })
36
- fs.writeFileSync(configPath, JSON.stringify({ apiKey: key }, null, 2), 'utf-8')
37
- }
40
+ if (!process.stdin.isTTY) process.exit(0)
38
41
 
39
- // Skip if already set up or not in an interactive terminal
40
- if (alreadyConfigured() || !process.stdin.isTTY) {
41
- process.exit(0)
42
- }
42
+ const config = loadConfig()
43
+ const needsApiKey = !hasApiKey(config)
44
+ const needsDeveloperInfo = !config.developerName
45
+
46
+ if (!needsApiKey && !needsDeveloperInfo) process.exit(0)
43
47
 
44
48
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
45
49
 
50
+ function ask(question) {
51
+ return new Promise(resolve => rl.question(question, answer => resolve(answer.trim())))
52
+ }
53
+
46
54
  console.log('\n──────────────────────────────────────────')
47
55
  console.log(' client-handover setup')
48
56
  console.log('──────────────────────────────────────────')
49
- console.log(' To generate documents, you need an Anthropic API key.')
50
- console.log(' Get one free at: https://console.anthropic.com\n')
57
+ console.log(' This tool generates professional handover documents for client websites.\n')
51
58
 
52
- rl.question(' Enter your Anthropic API key (or press Enter to skip): ', (answer) => {
53
- rl.close()
54
- const key = answer.trim()
59
+ async function run() {
60
+ if (needsApiKey) {
61
+ console.log(' To generate documents you need an Anthropic API key.')
62
+ console.log(' Get one free at: https://console.anthropic.com\n')
63
+ const key = await ask(' Enter your Anthropic API key (or press Enter to skip): ')
64
+ if (key) {
65
+ config.apiKey = key
66
+ console.log(' ✓ API key saved.\n')
67
+ } else {
68
+ console.log(' Skipped. Run "handover key <your-api-key>" at any time.\n')
69
+ }
70
+ }
71
+
72
+ if (needsDeveloperInfo) {
73
+ console.log(' Developer details will appear in all generated handover documents.\n')
74
+ const name = await ask(' Your full name (developer): ')
75
+ if (name) config.developerName = name
55
76
 
56
- if (!key) {
57
- console.log('\n Skipped. Run "handover key <your-api-key>" at any time to set it.\n')
58
- process.exit(0)
77
+ const company = await ask(' Your company name (or press Enter if not applicable): ')
78
+ if (company) config.developerCompany = company
79
+
80
+ const email = await ask(' Your email address: ')
81
+ if (email) config.developerEmail = email
82
+
83
+ const phone = await ask(' Your phone number (optional, press Enter to skip): ')
84
+ if (phone) config.developerPhone = phone
85
+
86
+ console.log()
59
87
  }
60
88
 
61
- saveKey(key)
62
- console.log('\n✅ API key saved. Run "handover handover" to get started.\n')
89
+ rl.close()
90
+ saveConfig(config)
91
+ console.log(' ✅ Setup complete. Run "handover /create" in any project folder to generate handover documents.\n')
63
92
  process.exit(0)
64
- })
93
+ }
94
+
95
+ run()
package/prompts.js ADDED
@@ -0,0 +1,185 @@
1
+ export function technicalHandoverPrompt(projectInfo, developerInfo) {
2
+ const { name, company, email, phone } = developerInfo
3
+ const date = new Date().toLocaleDateString('en-GB', { day: 'numeric', month: 'long', year: 'numeric' })
4
+
5
+ return `
6
+ You are an expert technical writer generating a TECHNICAL HANDOVER DOCUMENT for a developer taking over a client website project.
7
+
8
+ This document is written for a DEVELOPER (future maintainer) — be precise, thorough, and technically complete. They need to be able to pick this project up cold with no prior knowledge.
9
+
10
+ Developer who built this project:
11
+ - Name: ${name || '[Developer Name]'}
12
+ ${company ? `- Company: ${company}` : ''}
13
+ - Email: ${email || '[Developer Email]'}
14
+ ${phone ? `- Phone: ${phone}` : ''}
15
+ - Handover date: ${date}
16
+
17
+ The following context has been automatically scanned from the project files (package.json, config files, folder structure, environment variable keys, deploy configs, README, CSS colors, etc.). Use this to generate a document specific to THIS project. Do not invent or use generic placeholders — if a specific detail is not in the scanned data, note it as "[to be confirmed]".
18
+
19
+ ${projectInfo || '[No project data available]'}
20
+
21
+ Generate a complete technical handover document with ALL of the following sections:
22
+
23
+ ---
24
+
25
+ # [Project Name] — Technical Handover Document
26
+
27
+ **Prepared by:** ${name || '[Developer Name]'}${company ? ` · ${company}` : ''}${email ? ` · ${email}` : ''}${phone ? ` · ${phone}` : ''}
28
+ **Handover Date:** ${date}
29
+ **Project URL:** [Live URL — to be confirmed]
30
+ **Repository:** [Repo URL — to be confirmed]
31
+
32
+ ---
33
+
34
+ ## 1. Tech Stack
35
+ - List every technology, framework, library, and tool used
36
+ - Include versions where known
37
+ - Briefly explain the role of each in the project
38
+
39
+ ## 2. Project Structure
40
+ - Full folder/file structure with explanations of what each directory contains
41
+ - Location of key config files and what they control
42
+ - Entry points and routing overview
43
+
44
+ ## 3. Local Development Setup
45
+ - Prerequisites (Node version, package manager, etc.)
46
+ - Step-by-step install and run instructions using exact terminal commands in code blocks
47
+ - All environment variables required (keys only, no values) — what each one does
48
+
49
+ ## 4. Build & Deployment
50
+ - Build command and output directory
51
+ - Hosting provider and deployment method
52
+ - CI/CD pipeline if applicable
53
+ - Domain, DNS, and SSL details
54
+ - How to roll back a broken deployment
55
+
56
+ ## 5. Third-Party Integrations & Services
57
+ - Every external service, API, or plugin used
58
+ - What each one does and where it is configured
59
+ - Which accounts own these services
60
+
61
+ ## 6. Credentials & Access
62
+ - Table of all accounts needed (service, URL, account owner, how to request access)
63
+ - Environment variable names and their purpose
64
+ - Access transfer checklist
65
+
66
+ ## 7. Known Issues & Technical Debt
67
+ - Any bugs, limitations, or workarounds in the current codebase
68
+ - TODO items or deferred improvements
69
+ - Performance or SEO considerations
70
+
71
+ ## 8. Maintenance Guide
72
+ - How to update npm dependencies safely
73
+ - What to check after deploying changes
74
+ - Renewal dates for domain, hosting, or licences if known
75
+
76
+ ## 9. Developer Handover Checklist
77
+ - [ ] Repository access transferred or shared
78
+ - [ ] All environment variables documented
79
+ - [ ] Hosting and domain access transferred
80
+ - [ ] Third-party service accounts handed over
81
+ - [ ] Local dev environment tested and documented
82
+ - [ ] Live site tested across browsers and devices
83
+ - [ ] Analytics verified working
84
+ - [ ] README updated
85
+
86
+ ---
87
+
88
+ Format the entire document in clean, well-structured Markdown.
89
+ Use code blocks for all terminal commands and code snippets.
90
+ Use tables where appropriate (especially for credentials and dependencies).
91
+ Be thorough and precise — this document is the sole reference for the incoming developer.
92
+ `.trim()
93
+ }
94
+
95
+ export function nonTechnicalHandoverPrompt(projectInfo, developerInfo) {
96
+ const { name, company, email, phone } = developerInfo
97
+ const date = new Date().toLocaleDateString('en-GB', { day: 'numeric', month: 'long', year: 'numeric' })
98
+
99
+ return `
100
+ You are an expert writer generating a NON-TECHNICAL HANDOVER DOCUMENT for a CLIENT receiving their completed website.
101
+
102
+ This document is written for the BUSINESS OWNER or CLIENT — use plain English, a warm and reassuring tone, and absolutely no technical jargon. The client should feel confident and informed about what they own.
103
+
104
+ Developer contact details:
105
+ - Name: ${name || '[Developer Name]'}
106
+ ${company ? `- Company: ${company}` : ''}
107
+ - Email: ${email || '[Developer Email]'}
108
+ ${phone ? `- Phone: ${phone}` : ''}
109
+ - Handover date: ${date}
110
+
111
+ The following context has been automatically scanned from the project files. Use this to generate a document specific to THIS project. Focus on what the client needs to know — not how it was built. If a specific detail is not available, note it as "[to be confirmed]".
112
+
113
+ ${projectInfo || '[No project data available]'}
114
+
115
+ Generate a complete non-technical handover document with ALL of the following sections:
116
+
117
+ ---
118
+
119
+ # [Project Name] — Your Website Handover Guide
120
+
121
+ **Prepared by:** ${name || '[Developer Name]'}${company ? ` · ${company}` : ''}${email ? ` · ${email}` : ''}${phone ? ` · ${phone}` : ''}
122
+ **Date:** ${date}
123
+
124
+ ---
125
+
126
+ ## 1. About Your Website
127
+ - What your website does and who it is for (1–2 paragraphs, plain English)
128
+ - The main pages or sections and what each one is for
129
+ - Any special features the site has (contact forms, booking, e-commerce, blog, etc.)
130
+
131
+ ## 2. Your Website's Look & Feel
132
+ - The overall design style and visual identity
133
+ - Colours used on the site (mention specific colours by name and hex code if detected in the project)
134
+ - Fonts used
135
+ - Any branding guidelines to keep in mind when making future updates
136
+
137
+ ## 3. What You Own
138
+ - A plain-English summary of everything handed over: the website, domain, hosting, code, and content
139
+ - Who owns what and what that means for you
140
+
141
+ ## 4. Logging Into Your Website
142
+ - How to access and log in to any admin area or CMS (plain steps, no jargon)
143
+ - What you can safely update yourself
144
+ - What you should NOT change without speaking to a developer first
145
+
146
+ ## 5. How to Update Your Content
147
+ - Step-by-step instructions for common tasks (e.g. updating text, adding images, publishing a blog post)
148
+ - Keep instructions simple and numbered
149
+ - Include a note about backing up before making changes
150
+
151
+ ## 6. Your Accounts & Passwords
152
+ - List of all accounts the client now owns (hosting, domain, CMS, email, analytics, etc.)
153
+ - Reminder to store passwords securely and change them after handover
154
+ - Note: actual passwords should be shared separately and securely — not in this document
155
+
156
+ ## 7. Keeping Your Website Healthy
157
+ - What needs renewing and approximately when (domain name, hosting plan, SSL certificate, any paid plugins)
158
+ - How to tell if something on the site is broken
159
+ - Simple monthly and yearly maintenance checklist
160
+
161
+ ## 8. Getting Help
162
+ - When and how to contact your developer: ${name || '[Developer Name]'}${company ? ` (${company})` : ''}${email ? `, ${email}` : ''}${phone ? `, ${phone}` : ''}
163
+ - What kinds of changes require a developer
164
+ - Recommended process for requesting future updates
165
+
166
+ ## 9. Handover Sign-Off
167
+ ### Developer confirms:
168
+ - [ ] All accounts and login details have been transferred
169
+ - [ ] The live site has been reviewed and approved
170
+ - [ ] This document has been discussed with the client
171
+
172
+ ### Client acknowledges:
173
+ - [ ] I have received all login credentials securely
174
+ - [ ] I understand how to update my website content
175
+ - [ ] I know what I should not change without developer help
176
+ - [ ] I know how to contact my developer for support
177
+
178
+ ---
179
+
180
+ Write the entire document in plain, friendly English. Use short sentences and simple words.
181
+ Avoid ALL technical jargon — if a technical term must be used, explain it in brackets immediately after.
182
+ Use numbered lists for any step-by-step instructions.
183
+ The tone should be warm, professional, and reassuring — the client should feel proud of their new website.
184
+ `.trim()
185
+ }
package/scanner.js CHANGED
@@ -182,6 +182,13 @@ export function scanProject(dir = process.cwd()) {
182
182
  }
183
183
  }
184
184
 
185
+ // --- CSS color detection ---
186
+ const cssColors = extractColors(dir)
187
+ if (cssColors.length) {
188
+ sections.push(`\n## Detected colours (from CSS/config files)`)
189
+ cssColors.forEach(c => sections.push(` ${c}`))
190
+ }
191
+
185
192
  // --- Folder structure ---
186
193
  const structure = getFolderStructure(dir)
187
194
  if (structure.length) {
@@ -191,3 +198,53 @@ export function scanProject(dir = process.cwd()) {
191
198
 
192
199
  return sections.join('\n')
193
200
  }
201
+
202
+ function extractColors(dir) {
203
+ const colors = new Set()
204
+ const hexPattern = /#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})\b/g
205
+ const cssVarColorPattern = /(--[\w-]*color[\w-]*|--[\w-]*bg[\w-]*|--[\w-]*primary[\w-]*|--[\w-]*secondary[\w-]*):\s*([^;}\n]+)/gi
206
+ const cssExtensions = ['.css', '.scss', '.sass', '.less']
207
+
208
+ function scanFile(filePath) {
209
+ const content = readFileSafe(filePath)
210
+ if (!content) return
211
+ let match
212
+ while ((match = hexPattern.exec(content)) !== null) {
213
+ colors.add(`#${match[1].toUpperCase()}`)
214
+ }
215
+ while ((match = cssVarColorPattern.exec(content)) !== null) {
216
+ colors.add(`${match[1].trim()}: ${match[2].trim()}`)
217
+ }
218
+ }
219
+
220
+ function walkDir(d, depth = 0) {
221
+ if (depth > 3) return
222
+ const ignore = new Set(['node_modules', '.git', '.next', '.nuxt', 'dist', 'build', '.cache'])
223
+ try {
224
+ const items = fs.readdirSync(d, { withFileTypes: true })
225
+ for (const item of items) {
226
+ if (ignore.has(item.name) || item.name.startsWith('.')) continue
227
+ const full = path.join(d, item.name)
228
+ if (item.isDirectory()) {
229
+ walkDir(full, depth + 1)
230
+ } else if (cssExtensions.includes(path.extname(item.name).toLowerCase())) {
231
+ scanFile(full)
232
+ }
233
+ }
234
+ } catch {}
235
+ }
236
+
237
+ walkDir(dir)
238
+
239
+ // Also check tailwind config for color definitions
240
+ for (const twConfig of ['tailwind.config.js', 'tailwind.config.ts']) {
241
+ const content = readFileSafe(path.join(dir, twConfig))
242
+ if (!content) continue
243
+ let match
244
+ while ((match = hexPattern.exec(content)) !== null) {
245
+ colors.add(`#${match[1].toUpperCase()}`)
246
+ }
247
+ }
248
+
249
+ return [...colors].slice(0, 40) // cap at 40 to avoid flooding context
250
+ }
package/credentials.js DELETED
@@ -1,51 +0,0 @@
1
- export function credentials(projectInfo = '') {
2
- return `
3
- You are a technical documentation writer creating a CREDENTIALS section for a frontend website handover document.
4
-
5
- ⚠️ IMPORTANT: Never include real passwords, keys, or secrets. All sensitive values must use placeholder format: YOUR_VALUE_HERE or [REPLACE_WITH_ACTUAL_VALUE]
6
-
7
- The following context has been automatically scanned from the developer's actual project files. Use the real environment variable keys and detected services to build the credentials table for THIS specific project. Do not invent services that are not present. Mark values as "[to be filled in]".
8
-
9
- ${projectInfo || '[No project data available — use placeholder examples]'}
10
-
11
- Generate a CREDENTIALS & ACCESS section that includes:
12
-
13
- ## Credentials & Access
14
-
15
- ### For the Client (Plain English)
16
- - What accounts exist and what each one is for
17
- - Who currently has admin access
18
- - How to reset a password if they get locked out
19
- - Reminder to store credentials in a password manager (suggest 1Password, Bitwarden, etc.)
20
- - What NOT to share over email
21
-
22
- ### For the Developer (Technical)
23
- Generate a credentials table template with these categories (use placeholders for all values):
24
-
25
- | Service | Purpose | URL | Username/Email | Notes |
26
- |---------|---------|-----|----------------|-------|
27
-
28
- Include rows for:
29
- - Hosting provider (e.g. Vercel, Netlify, cPanel)
30
- - Domain registrar
31
- - CMS or admin panel (if applicable)
32
- - Database access (if applicable)
33
- - Third-party APIs (list each one)
34
- - Email/SMTP service
35
- - Analytics (e.g. Google Analytics)
36
- - Version control (GitHub/GitLab repo URL)
37
- - Any other relevant service
38
-
39
- ### API Keys & Environment Variables
40
- List all .env variables used with placeholder values and a description of each.
41
-
42
- ### Access Transfer Checklist
43
- - [ ] Client has been added as admin to hosting
44
- - [ ] Client has been added to domain registrar
45
- - [ ] Developer has been removed or downgraded after handover
46
- - [ ] 2FA has been set up for client accounts
47
- - [ ] All credentials have been shared via secure method (not email)
48
-
49
- Format as clean Markdown with tables.
50
- `.trim()
51
- }
package/deploy.js DELETED
@@ -1,35 +0,0 @@
1
- export function deploy(projectInfo = '') {
2
- return `
3
- You are a technical documentation writer creating a DEPLOYMENT section for a frontend website handover document.
4
-
5
- The audience is TWO types of readers:
6
- 1. A NON-TECHNICAL CLIENT — plain English, no jargon
7
- 2. A DEVELOPER — exact technical steps
8
-
9
- The following context has been automatically scanned from the developer's actual project files (deploy configs, package.json scripts, workflow files, etc.). Use it to generate deployment documentation specific to THIS project — do not use generic placeholders. Mark any missing details as "[to be filled in]".
10
-
11
- ${projectInfo || '[No project data available — use placeholder examples]'}
12
-
13
- Generate a DEPLOYMENT section that includes:
14
-
15
- ## Deployment & Hosting
16
-
17
- ### For the Client (Plain English)
18
- - Where the website is hosted and what that means
19
- - How updates get published (automatic or manual?)
20
- - What happens if the site goes down — who to call, what to check
21
- - Estimated monthly/yearly hosting costs if known
22
-
23
- ### For the Developer (Technical)
24
- - Hosting provider and account details (use placeholders for sensitive info)
25
- - Deployment method (e.g. Vercel push-to-deploy, FTP, CI/CD pipeline)
26
- - Step-by-step deploy process with exact commands
27
- - Domain/DNS setup and where it's managed
28
- - SSL certificate info
29
- - Build command and output directory
30
- - Any environment-specific configs (staging vs production)
31
- - Rollback procedure if a deployment fails
32
-
33
- Format as clean Markdown. Use code blocks for terminal commands.
34
- `.trim()
35
- }
package/handover.js DELETED
@@ -1,97 +0,0 @@
1
- export function handover(projectInfo = '') {
2
- return `
3
- You are an expert technical writer generating a COMPLETE CLIENT WEBSITE HANDOVER DOCUMENT.
4
-
5
- This document is for TWO audiences:
6
- 1. NON-TECHNICAL CLIENT — plain English, reassuring tone, no jargon. They need to feel confident owning this site.
7
- 2. DEVELOPER (future maintainer) — precise, technical, complete. They need to be able to pick this up cold.
8
-
9
- The following context has been automatically scanned from the developer's actual project files (package.json, config files, folder structure, environment variable keys, deploy configs, README, etc.). Use this to generate a document specific to THIS project — do not invent or use generic placeholder examples. If a specific detail is not available in the scanned data, note it as "[to be filled in]" rather than guessing.
10
-
11
- ${projectInfo || '[No project data available — use placeholder examples throughout]'}
12
-
13
- Generate a full handover document with ALL of the following sections:
14
-
15
- ---
16
-
17
- # [Project Name] — Website Handover Document
18
- **Prepared by:** [Developer Name]
19
- **Handover Date:** [Date]
20
- **Project URL:** [Live URL]
21
- **Repository:** [Repo URL]
22
-
23
- ---
24
-
25
- ## 1. Project Overview
26
- - What the site does (1 paragraph, plain English)
27
- - Tech stack summary (with one-line plain English explanation of each)
28
- - Key contacts (developer, hosting support, etc.)
29
-
30
- ## 2. Project Setup & Dependencies
31
- *(Follow the dual-audience format: client section + developer section)*
32
- - Prerequisites, install steps, local dev commands, environment variables
33
-
34
- ## 3. Deployment & Hosting
35
- *(Dual-audience format)*
36
- - Hosting provider, deploy method, domain/DNS, SSL, build config, rollback steps
37
-
38
- ## 4. Credentials & Access
39
- *(Dual-audience format)*
40
- - All accounts, credentials table with placeholders, .env variables, access transfer checklist
41
-
42
- ## 5. Site Structure & Content Management
43
- ### For the Client
44
- - How to update content (CMS, direct file edits, or contact developer)
45
- - What they should NEVER touch without developer help
46
- - How to add new pages or blog posts (if applicable)
47
-
48
- ### For the Developer
49
- - Folder structure overview
50
- - Where key config files live
51
- - Component/template structure
52
- - Any custom hooks, stores, or utilities worth knowing about
53
-
54
- ## 6. Maintenance & Updates
55
- ### For the Client
56
- - What needs renewing and when (domain, hosting, licences)
57
- - How to know if something is broken
58
- - Recommended monthly/yearly maintenance tasks
59
-
60
- ### For the Developer
61
- - How to update npm dependencies safely
62
- - Any known technical debt or TODO items
63
- - Performance and SEO considerations
64
-
65
- ## 7. Licensing & Attribution
66
- *(Dual-audience format)*
67
- - Ownership summary, third-party licences table, attribution requirements, portfolio rights
68
-
69
- ## 8. Handover Checklist
70
- A final sign-off checklist for both parties:
71
-
72
- ### Developer Checklist (before handover)
73
- - [ ] All credentials transferred securely
74
- - [ ] Client added to hosting & domain accounts
75
- - [ ] Repository access transferred or shared
76
- - [ ] Live site tested across browsers and devices
77
- - [ ] Analytics verified working
78
- - [ ] All placeholder content replaced
79
- - [ ] .env.example file committed to repo
80
- - [ ] README updated
81
- - [ ] Handover document reviewed with client
82
-
83
- ### Client Acknowledgement
84
- - [ ] I have received all login credentials
85
- - [ ] I understand how to update content
86
- - [ ] I know who to contact for technical support
87
- - [ ] I have been shown the live site and approve it
88
-
89
- ---
90
-
91
- Format the entire document in clean, well-structured Markdown.
92
- Use code blocks for all terminal commands.
93
- Use tables where appropriate.
94
- Keep client sections warm, clear, and jargon-free.
95
- Keep developer sections precise and thorough.
96
- `.trim()
97
- }
package/setup.js DELETED
@@ -1,32 +0,0 @@
1
- export function setup(projectInfo = '') {
2
- return `
3
- You are a technical documentation writer creating a SETUP section for a frontend website handover document.
4
-
5
- The audience is TWO types of readers:
6
- 1. A NON-TECHNICAL CLIENT — who needs plain-English explanations of what everything is and why it matters
7
- 2. A DEVELOPER — who needs exact commands, file paths, and technical detail
8
-
9
- The following context has been automatically scanned from the developer's actual project files. Use it to generate documentation specific to THIS project — do not use generic placeholders. If something is unclear from the scanned data, mark it as "[to be filled in]".
10
-
11
- ${projectInfo || '[No project data available — use placeholder examples]'}
12
-
13
- Generate a SETUP section that includes:
14
-
15
- ## Project Setup & Dependencies
16
-
17
- ### For the Client (Plain English)
18
- - What tech stack is used and a simple one-line explanation of each part
19
- - What they'll need to pay for or maintain (hosting, domain, licences)
20
- - Who to contact if something breaks
21
-
22
- ### For the Developer (Technical)
23
- - Prerequisites (Node version, package manager, etc.)
24
- - Step-by-step install instructions with exact terminal commands
25
- - Environment variables needed (use placeholder values like YOUR_VALUE_HERE)
26
- - How to run locally (dev server command, expected URL)
27
- - Any known gotchas or first-time setup issues
28
-
29
- Format this as clean, well-structured Markdown with clear headings.
30
- Keep the client section jargon-free. Keep the developer section precise.
31
- `.trim()
32
- }