client-handover 1.0.5 → 1.0.6

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,11 @@ client-handover/
200
200
 
201
201
  ## Changelog
202
202
 
203
+ ### 1.0.6
204
+ - Auto-scans your actual project files on every run — reads package.json, config files, env variable keys, deploy configs, folder structure, and more
205
+ - Generated documents are now tailored to your real project, not generic templates
206
+ - Extra notes file still supported as optional additional context
207
+
203
208
  ### 1.0.5
204
209
  - Interactive API key setup prompt on install — no manual config needed
205
210
  - Added `handover key <api-key>` command to save key at any time
package/cli.js CHANGED
@@ -6,6 +6,7 @@ import { credentials } from './credentials.js'
6
6
  import { handover } from './handover.js'
7
7
  import { license } from './license.js'
8
8
  import { generateDoc, saveApiKey } from './generator.js'
9
+ import { scanProject } from './scanner.js'
9
10
  import chalk from 'chalk'
10
11
  import fs from 'fs'
11
12
  import path from 'path'
@@ -42,7 +43,7 @@ function printHelp() {
42
43
  console.log()
43
44
  }
44
45
 
45
- async function runAll(projectInfo, folderName) {
46
+ async function runAll(projectInfo, folderName, outputName) {
46
47
  const sections = [
47
48
  { key: 'handover', fn: handover, label: 'Full Handover Document' },
48
49
  { key: 'setup', fn: setup, label: 'Project Setup & Dependencies' },
@@ -86,17 +87,23 @@ async function main() {
86
87
  process.exit(0)
87
88
  }
88
89
 
89
- // Read optional project info file
90
- let projectInfo = ''
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 = ''
91
96
  if (infoFile) {
92
97
  if (!fs.existsSync(infoFile)) {
93
98
  console.error(chalk.red(`\n❌ File not found: ${infoFile}\n`))
94
99
  process.exit(1)
95
100
  }
96
- projectInfo = fs.readFileSync(infoFile, 'utf-8')
97
- console.log(chalk.green(`\n📄 Loaded project info from: ${infoFile}`))
101
+ extraInfo = fs.readFileSync(infoFile, 'utf-8')
102
+ console.log(chalk.green(`📄 Loaded extra info from: ${infoFile}`))
98
103
  }
99
104
 
105
+ const projectInfo = [scannedContext, extraInfo].filter(Boolean).join('\n\n---\n\n')
106
+
100
107
  if (command === 'all') {
101
108
  const folderName = outputName || 'all'
102
109
  try {
package/credentials.js CHANGED
@@ -4,8 +4,9 @@ You are a technical documentation writer creating a CREDENTIALS section for a fr
4
4
 
5
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
6
 
7
- Using the following project information:
8
- ${projectInfo || '[No project info provided — use placeholder examples]'}
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]'}
9
10
 
10
11
  Generate a CREDENTIALS & ACCESS section that includes:
11
12
 
package/deploy.js CHANGED
@@ -6,8 +6,9 @@ The audience is TWO types of readers:
6
6
  1. A NON-TECHNICAL CLIENT — plain English, no jargon
7
7
  2. A DEVELOPER — exact technical steps
8
8
 
9
- Using the following project information:
10
- ${projectInfo || '[No project info provided — use placeholder examples]'}
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]'}
11
12
 
12
13
  Generate a DEPLOYMENT section that includes:
13
14
 
package/handover.js CHANGED
@@ -6,8 +6,9 @@ This document is for TWO audiences:
6
6
  1. NON-TECHNICAL CLIENT — plain English, reassuring tone, no jargon. They need to feel confident owning this site.
7
7
  2. DEVELOPER (future maintainer) — precise, technical, complete. They need to be able to pick this up cold.
8
8
 
9
- Using the following project information from the developer:
10
- ${projectInfo || '[No project info provided — use placeholder examples throughout]'}
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]'}
11
12
 
12
13
  Generate a full handover document with ALL of the following sections:
13
14
 
package/license.js CHANGED
@@ -2,8 +2,9 @@ export function license(projectInfo = '') {
2
2
  return `
3
3
  You are a technical documentation writer creating a LICENSING & ATTRIBUTION section for a frontend website handover document.
4
4
 
5
- Using the following project information:
6
- ${projectInfo || '[No project info provided — use placeholder examples]'}
5
+ The following context has been automatically scanned from the developer's actual project files. Use the real dependencies, license file, and detected libraries to generate licensing documentation specific to THIS project — do not use generic placeholder libraries.
6
+
7
+ ${projectInfo || '[No project data available — use placeholder examples]'}
7
8
 
8
9
  Generate a LICENSING & ATTRIBUTION section that includes:
9
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "client-handover",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "description": "AI-powered handover document generator for frontend developers handing off client websites",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -17,6 +17,7 @@
17
17
  "credentials.js",
18
18
  "license.js",
19
19
  "postinstall.js",
20
+ "scanner.js",
20
21
  "README.md"
21
22
  ],
22
23
  "scripts": {
package/scanner.js ADDED
@@ -0,0 +1,193 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+
4
+ const CONFIG_FILES = [
5
+ 'vite.config.js', 'vite.config.ts',
6
+ 'next.config.js', 'next.config.ts', 'next.config.mjs',
7
+ 'nuxt.config.js', 'nuxt.config.ts',
8
+ 'astro.config.js', 'astro.config.ts', 'astro.config.mjs',
9
+ 'svelte.config.js',
10
+ 'remix.config.js',
11
+ 'tailwind.config.js', 'tailwind.config.ts',
12
+ 'postcss.config.js',
13
+ 'netlify.toml',
14
+ 'vercel.json',
15
+ 'render.yaml',
16
+ 'railway.json',
17
+ '.htaccess',
18
+ 'Dockerfile',
19
+ 'docker-compose.yml',
20
+ 'firebase.json',
21
+ ]
22
+
23
+ const DEPLOY_WORKFLOW_DIR = '.github/workflows'
24
+
25
+ function readFileSafe(filePath) {
26
+ try {
27
+ return fs.readFileSync(filePath, 'utf-8')
28
+ } catch {
29
+ return null
30
+ }
31
+ }
32
+
33
+ function getEnvKeys(filePath) {
34
+ const content = readFileSafe(filePath)
35
+ if (!content) return []
36
+ return content
37
+ .split('\n')
38
+ .map(l => l.trim())
39
+ .filter(l => l && !l.startsWith('#') && l.includes('='))
40
+ .map(l => l.split('=')[0].trim())
41
+ }
42
+
43
+ function getFolderStructure(dir, depth = 0, maxDepth = 2) {
44
+ if (depth > maxDepth) return []
45
+ const ignore = new Set(['node_modules', '.git', '.next', '.nuxt', 'dist', 'build', '.cache', 'coverage', '.turbo'])
46
+ let entries = []
47
+ try {
48
+ const items = fs.readdirSync(dir, { withFileTypes: true })
49
+ for (const item of items) {
50
+ if (ignore.has(item.name) || item.name.startsWith('.')) continue
51
+ const indent = ' '.repeat(depth)
52
+ if (item.isDirectory()) {
53
+ entries.push(`${indent}${item.name}/`)
54
+ entries.push(...getFolderStructure(path.join(dir, item.name), depth + 1, maxDepth))
55
+ } else {
56
+ entries.push(`${indent}${item.name}`)
57
+ }
58
+ }
59
+ } catch {}
60
+ return entries
61
+ }
62
+
63
+ function detectFramework(deps = {}, devDeps = {}) {
64
+ const all = { ...deps, ...devDeps }
65
+ if (all['next']) return 'Next.js'
66
+ if (all['nuxt'] || all['nuxt3']) return 'Nuxt'
67
+ if (all['@astrojs/core'] || all['astro']) return 'Astro'
68
+ if (all['@sveltejs/kit']) return 'SvelteKit'
69
+ if (all['svelte']) return 'Svelte'
70
+ if (all['@remix-run/react']) return 'Remix'
71
+ if (all['gatsby']) return 'Gatsby'
72
+ if (all['react']) return 'React'
73
+ if (all['vue']) return 'Vue'
74
+ if (all['angular']) return 'Angular'
75
+ return null
76
+ }
77
+
78
+ export function scanProject(dir = process.cwd()) {
79
+ const sections = []
80
+
81
+ // --- package.json ---
82
+ const pkgPath = path.join(dir, 'package.json')
83
+ let pkg = null
84
+ if (fs.existsSync(pkgPath)) {
85
+ try {
86
+ pkg = JSON.parse(readFileSafe(pkgPath))
87
+ const framework = detectFramework(pkg.dependencies, pkg.devDependencies)
88
+ sections.push(`## package.json`)
89
+ sections.push(`Name: ${pkg.name || 'unknown'}`)
90
+ if (pkg.description) sections.push(`Description: ${pkg.description}`)
91
+ if (pkg.version) sections.push(`Version: ${pkg.version}`)
92
+ if (framework) sections.push(`Detected framework: ${framework}`)
93
+ if (pkg.engines?.node) sections.push(`Node requirement: ${pkg.engines.node}`)
94
+
95
+ if (pkg.scripts && Object.keys(pkg.scripts).length) {
96
+ sections.push(`\nScripts:`)
97
+ for (const [k, v] of Object.entries(pkg.scripts)) {
98
+ sections.push(` ${k}: ${v}`)
99
+ }
100
+ }
101
+
102
+ const deps = Object.keys(pkg.dependencies || {})
103
+ const devDeps = Object.keys(pkg.devDependencies || {})
104
+ if (deps.length) sections.push(`\nDependencies: ${deps.join(', ')}`)
105
+ if (devDeps.length) sections.push(`Dev dependencies: ${devDeps.join(', ')}`)
106
+ } catch {}
107
+ }
108
+
109
+ // --- Package manager ---
110
+ let pm = 'npm'
111
+ if (fs.existsSync(path.join(dir, 'yarn.lock'))) pm = 'yarn'
112
+ else if (fs.existsSync(path.join(dir, 'pnpm-lock.yaml'))) pm = 'pnpm'
113
+ else if (fs.existsSync(path.join(dir, 'bun.lockb'))) pm = 'bun'
114
+ sections.push(`\nPackage manager: ${pm}`)
115
+
116
+ // --- Environment variables ---
117
+ const envExampleKeys = getEnvKeys(path.join(dir, '.env.example'))
118
+ const envLocalKeys = getEnvKeys(path.join(dir, '.env.local'))
119
+ const envKeys = getEnvKeys(path.join(dir, '.env'))
120
+ const allEnvKeys = [...new Set([...envExampleKeys, ...envLocalKeys, ...envKeys])]
121
+ if (allEnvKeys.length) {
122
+ sections.push(`\n## Environment Variables (keys only)`)
123
+ allEnvKeys.forEach(k => sections.push(` ${k}`))
124
+ }
125
+
126
+ // --- Config files ---
127
+ for (const file of CONFIG_FILES) {
128
+ const filePath = path.join(dir, file)
129
+ if (fs.existsSync(filePath)) {
130
+ const content = readFileSafe(filePath)
131
+ if (content && content.length < 4000) {
132
+ sections.push(`\n## ${file}`)
133
+ sections.push('```')
134
+ sections.push(content.trim())
135
+ sections.push('```')
136
+ } else if (content) {
137
+ sections.push(`\n## ${file} (truncated)`)
138
+ sections.push('```')
139
+ sections.push(content.trim().slice(0, 4000) + '\n...')
140
+ sections.push('```')
141
+ }
142
+ }
143
+ }
144
+
145
+ // --- GitHub Actions workflows ---
146
+ const workflowDir = path.join(dir, DEPLOY_WORKFLOW_DIR)
147
+ if (fs.existsSync(workflowDir)) {
148
+ try {
149
+ const files = fs.readdirSync(workflowDir).filter(f => f.endsWith('.yml') || f.endsWith('.yaml'))
150
+ for (const file of files) {
151
+ const content = readFileSafe(path.join(workflowDir, file))
152
+ if (content) {
153
+ sections.push(`\n## .github/workflows/${file}`)
154
+ sections.push('```yaml')
155
+ sections.push(content.trim().slice(0, 3000))
156
+ sections.push('```')
157
+ }
158
+ }
159
+ } catch {}
160
+ }
161
+
162
+ // --- README ---
163
+ const readmePath = path.join(dir, 'README.md')
164
+ if (fs.existsSync(readmePath)) {
165
+ const content = readFileSafe(readmePath)
166
+ if (content) {
167
+ sections.push(`\n## README.md`)
168
+ sections.push(content.trim().slice(0, 3000))
169
+ }
170
+ }
171
+
172
+ // --- License ---
173
+ for (const f of ['LICENSE', 'LICENSE.md', 'LICENSE.txt']) {
174
+ const p = path.join(dir, f)
175
+ if (fs.existsSync(p)) {
176
+ const content = readFileSafe(p)
177
+ if (content) {
178
+ sections.push(`\n## ${f}`)
179
+ sections.push(content.trim().slice(0, 500))
180
+ }
181
+ break
182
+ }
183
+ }
184
+
185
+ // --- Folder structure ---
186
+ const structure = getFolderStructure(dir)
187
+ if (structure.length) {
188
+ sections.push(`\n## Project folder structure`)
189
+ sections.push(structure.join('\n'))
190
+ }
191
+
192
+ return sections.join('\n')
193
+ }
package/setup.js CHANGED
@@ -6,8 +6,9 @@ The audience is TWO types of readers:
6
6
  1. A NON-TECHNICAL CLIENT — who needs plain-English explanations of what everything is and why it matters
7
7
  2. A DEVELOPER — who needs exact commands, file paths, and technical detail
8
8
 
9
- Using the following project information provided by the developer:
10
- ${projectInfo || '[No project info provided — use placeholder examples]'}
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]'}
11
12
 
12
13
  Generate a SETUP section that includes:
13
14