@axiomatic-labs/claudeflow 2.4.0 → 2.4.1

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/bin/cli.js CHANGED
@@ -1,24 +1,21 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // npx entry point: handles install, update, and version commands directly.
3
+ // npx entry point: handles install/update and version commands directly.
4
4
  // No Go binary — downloads ZIP and extracts skill files to disk.
5
5
 
6
6
  const path = require('path');
7
7
 
8
8
  async function main() {
9
9
  const args = process.argv.slice(2);
10
- const command = args[0] || 'install';
10
+ const command = args[0] || '';
11
11
 
12
12
  switch (command) {
13
+ case '':
13
14
  case 'install':
14
- case 'init': {
15
- const init = require('../lib/init.js');
16
- await init();
17
- break;
18
- }
15
+ case 'init':
19
16
  case 'update': {
20
- const update = require('../lib/update.js');
21
- await update();
17
+ const install = require('../lib/install.js');
18
+ await install();
22
19
  break;
23
20
  }
24
21
  case 'version':
@@ -34,9 +31,9 @@ async function main() {
34
31
  console.log(' Usage: npx @axiomatic-labs/claudeflow [command]');
35
32
  console.log('');
36
33
  console.log(' Commands:');
37
- console.log(` ${ui.CYAN}install${ui.RESET} Download and extract Claudeflow (default)`);
38
- console.log(` ${ui.CYAN}update${ui.RESET} Re-download and update template files`);
39
- console.log(` ${ui.CYAN}version${ui.RESET} Show version info`);
34
+ console.log(` ${ui.CYAN}(default)${ui.RESET} Install or update Claudeflow`);
35
+ console.log(` ${ui.CYAN}update${ui.RESET} Alias for default (re-download template files)`);
36
+ console.log(` ${ui.CYAN}version${ui.RESET} Show version info`);
40
37
  console.log('');
41
38
  break;
42
39
  }
package/lib/install.js ADDED
@@ -0,0 +1,230 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { execSync } = require('child_process');
4
+ const { requireAuth } = require('./auth.js');
5
+ const { getLatestRelease, downloadReleaseAsset } = require('./download.js');
6
+ const { writeLocalVersion, readLocalVersion } = require('./version.js');
7
+ const ui = require('./ui.js');
8
+
9
+ // Template skills that ship with the ZIP — only these get overwritten.
10
+ // User-generated skills (from /setup, /update) are preserved.
11
+ const TEMPLATE_SKILLS = new Set([
12
+ 'claudeflow-init',
13
+ 'claudeflow-update',
14
+ 'claudeflow-design-tokens',
15
+ 'claudeflow-create-ui',
16
+ 'claudeflow-checkpoints',
17
+ ]);
18
+
19
+ async function run() {
20
+ const current = readLocalVersion();
21
+ const isUpdate = !!current;
22
+
23
+ ui.banner(current || undefined);
24
+
25
+ ui.step('Authenticating with GitHub...');
26
+ const token = await requireAuth();
27
+
28
+ ui.step('Fetching latest release...');
29
+ const release = await getLatestRelease(token);
30
+ const version = release.tag_name;
31
+
32
+ // Find the ZIP asset
33
+ const asset = release.assets.find((a) => a.name.endsWith('.zip'));
34
+ if (!asset) {
35
+ ui.error('No ZIP asset found in the latest release.');
36
+ process.exit(1);
37
+ }
38
+
39
+ ui.step(`Downloading ${version}...`);
40
+ const zipBuffer = await downloadReleaseAsset(asset, token);
41
+
42
+ // Write ZIP to temp file and extract to a temp directory
43
+ const tmpZip = path.join(require('os').tmpdir(), `claudeflow-${Date.now()}.zip`);
44
+ const tmpExtract = path.join(require('os').tmpdir(), `claudeflow-extract-${Date.now()}`);
45
+ fs.writeFileSync(tmpZip, zipBuffer);
46
+ fs.mkdirSync(tmpExtract, { recursive: true });
47
+
48
+ try {
49
+ ui.step('Extracting template files...');
50
+ execSync(`unzip -o "${tmpZip}" -d "${tmpExtract}"`, { stdio: 'pipe' });
51
+
52
+ const cwd = process.cwd();
53
+ const srcClaude = path.join(tmpExtract, '.claude');
54
+
55
+ // Copy settings.json
56
+ const srcSettings = path.join(srcClaude, 'settings.json');
57
+ const dstSettings = path.join(cwd, '.claude', 'settings.json');
58
+ fs.mkdirSync(path.join(cwd, '.claude'), { recursive: true });
59
+ if (fs.existsSync(srcSettings)) {
60
+ fs.copyFileSync(srcSettings, dstSettings);
61
+ }
62
+
63
+ // Copy template skills only (safe for both fresh + update — fresh has no user skills)
64
+ const srcSkills = path.join(srcClaude, 'skills');
65
+ const dstSkills = path.join(cwd, '.claude', 'skills');
66
+ if (fs.existsSync(srcSkills)) {
67
+ fs.mkdirSync(dstSkills, { recursive: true });
68
+ const skillDirs = fs.readdirSync(srcSkills, { withFileTypes: true });
69
+ for (const entry of skillDirs) {
70
+ if (entry.isDirectory() && TEMPLATE_SKILLS.has(entry.name)) {
71
+ copyDirSync(path.join(srcSkills, entry.name), path.join(dstSkills, entry.name));
72
+ }
73
+ }
74
+ }
75
+
76
+ // Copy all hooks
77
+ const srcHooks = path.join(srcClaude, 'hooks');
78
+ const dstHooks = path.join(cwd, '.claude', 'hooks');
79
+ if (fs.existsSync(srcHooks)) {
80
+ copyDirSync(srcHooks, dstHooks);
81
+ }
82
+
83
+ // Copy all docs
84
+ const srcDocs = path.join(srcClaude, 'docs');
85
+ const dstDocs = path.join(cwd, '.claude', 'docs');
86
+ if (fs.existsSync(srcDocs)) {
87
+ copyDirSync(srcDocs, dstDocs);
88
+ }
89
+
90
+ // Ensure .claude/tmp exists
91
+ fs.mkdirSync(path.join(cwd, '.claude', 'tmp'), { recursive: true });
92
+
93
+ // CLAUDE.md — only copy on fresh install (updates generate it via /setup and /update skills)
94
+ if (!isUpdate) {
95
+ const srcClaude_md = path.join(tmpExtract, 'CLAUDE.md');
96
+ const dstClaude_md = path.join(cwd, 'CLAUDE.md');
97
+ if (fs.existsSync(srcClaude_md)) {
98
+ fs.copyFileSync(srcClaude_md, dstClaude_md);
99
+ }
100
+ }
101
+
102
+ // Copy .mcp.json to project root
103
+ const srcMcp = path.join(tmpExtract, '.mcp.json');
104
+ const dstMcp = path.join(cwd, '.mcp.json');
105
+ if (fs.existsSync(srcMcp)) {
106
+ fs.copyFileSync(srcMcp, dstMcp);
107
+ }
108
+
109
+ // Write version file
110
+ writeLocalVersion(version);
111
+
112
+ } finally {
113
+ fs.unlinkSync(tmpZip);
114
+ fs.rmSync(tmpExtract, { recursive: true, force: true });
115
+ }
116
+
117
+ // Count installed items
118
+ const cwd = process.cwd();
119
+ const skillsDir = path.join(cwd, '.claude', 'skills');
120
+ let skillCount = 0;
121
+ try {
122
+ skillCount = fs.readdirSync(skillsDir, { withFileTypes: true }).filter((e) => e.isDirectory()).length;
123
+ } catch {}
124
+
125
+ const hooksDir = path.join(cwd, '.claude', 'hooks');
126
+ let hookCount = 0;
127
+ try {
128
+ hookCount = fs.readdirSync(hooksDir, { withFileTypes: true }).filter((e) => e.isDirectory()).length;
129
+ } catch {}
130
+
131
+ const docsDir = path.join(cwd, '.claude', 'docs');
132
+ let docCount = 0;
133
+ try {
134
+ docCount = fs.readdirSync(docsDir).filter((f) => !f.startsWith('.')).length;
135
+ } catch {}
136
+
137
+ console.log('');
138
+ if (isUpdate) {
139
+ ui.success(`${skillCount} skills (${TEMPLATE_SKILLS.size} template updated, user skills preserved)`);
140
+ ui.success(`${hookCount} hooks updated`);
141
+ ui.success(`${docCount} docs updated`);
142
+
143
+ if (current === version) {
144
+ ui.done(`Claudeflow ${version} reinstalled.`);
145
+ } else {
146
+ ui.done(`Claudeflow updated: ${current} → ${version}`);
147
+ }
148
+ } else {
149
+ ui.success(`${skillCount} skills installed`);
150
+ ui.success(`${hookCount} hooks installed`);
151
+ ui.success(`${docCount} docs installed`);
152
+
153
+ ui.done(`Claudeflow ${version} installed.`);
154
+
155
+ // Getting started guide with project detection
156
+ showGettingStarted(cwd);
157
+ }
158
+ }
159
+
160
+ function showGettingStarted(cwd) {
161
+ const MANIFESTS = ['package.json', 'pyproject.toml', 'Gemfile', 'go.mod', 'Cargo.toml', 'composer.json'];
162
+ const SKIP_DIRS = new Set(['node_modules', 'vendor', '__pycache__', 'dist', 'build', '.next', '.nuxt', '.output', '.claude']);
163
+
164
+ const rootHasManifest = MANIFESTS.some((f) => fs.existsSync(path.join(cwd, f)));
165
+
166
+ // Scan first-level subdirs for manifests (multi-stack detection)
167
+ const stackDirs = [];
168
+ try {
169
+ const entries = fs.readdirSync(cwd, { withFileTypes: true });
170
+ for (const entry of entries) {
171
+ if (!entry.isDirectory() || entry.name.startsWith('.') || SKIP_DIRS.has(entry.name)) continue;
172
+ if (MANIFESTS.some((f) => fs.existsSync(path.join(cwd, entry.name, f)))) {
173
+ stackDirs.push(entry.name);
174
+ }
175
+ }
176
+ } catch {}
177
+
178
+ const isMultiStack = stackDirs.length > 1 || (rootHasManifest && stackDirs.length > 0);
179
+ const isGreenfield = !rootHasManifest && stackDirs.length === 0;
180
+
181
+ console.log(` ${ui.BOLD}Getting started:${ui.RESET}`);
182
+ console.log('');
183
+ console.log(` 1. Run ${ui.CYAN}claude${ui.RESET} to start Claude Code`);
184
+
185
+ if (isGreenfield) {
186
+ console.log(` 2. Type ${ui.CYAN}/claudeflow-init${ui.RESET} to scaffold your project`);
187
+ console.log('');
188
+ console.log(` ${ui.DIM}Claudeflow will ask what you want to build, research${ui.RESET}`);
189
+ console.log(` ${ui.DIM}the best stack, and generate project-specific skills.${ui.RESET}`);
190
+ } else if (isMultiStack) {
191
+ console.log(` 2. Type ${ui.CYAN}/claudeflow-update${ui.RESET} to detect your stacks and generate project-specific skills`);
192
+ console.log('');
193
+ ui.info(`Multi-stack project detected: ${stackDirs.join(', ')}${rootHasManifest ? ' + root' : ''}`);
194
+ console.log('');
195
+ console.log(` ${ui.BOLD}After setup:${ui.RESET}`);
196
+ console.log(` ${ui.DIM}Just describe what you want — Claudeflow handles the rest.${ui.RESET}`);
197
+ } else {
198
+ // Single-stack existing project
199
+ console.log(` 2. Type ${ui.CYAN}/claudeflow-update${ui.RESET} to detect your stack and generate project-specific skills`);
200
+ console.log(` 3. Tell Claude what you want — it handles the rest`);
201
+ }
202
+
203
+ console.log('');
204
+ console.log(` ${ui.BOLD}Commands:${ui.RESET}`);
205
+ console.log(` ${ui.CYAN}/claudeflow-init${ui.RESET} ${ui.DIM}Scaffold a new project (greenfield)${ui.RESET}`);
206
+ console.log(` ${ui.CYAN}/claudeflow-update${ui.RESET} ${ui.DIM}Detect stack and generate skills for existing project${ui.RESET}`);
207
+ console.log(` ${ui.CYAN}/claudeflow-design-tokens${ui.RESET} ${ui.DIM}Create design reference from a site or screenshot${ui.RESET}`);
208
+ console.log(` ${ui.CYAN}/claudeflow-create-ui${ui.RESET} ${ui.DIM}Build pages from visual references${ui.RESET}`);
209
+ console.log(` ${ui.CYAN}/claudeflow-checkpoints${ui.RESET} ${ui.DIM}Auto-save work with git checkpoints${ui.RESET}`);
210
+ console.log('');
211
+ console.log(` ${ui.DIM}Docs: https://claudeflow.dev${ui.RESET}`);
212
+ console.log('');
213
+ }
214
+
215
+ // Recursively copy a directory, creating parents as needed
216
+ function copyDirSync(src, dst) {
217
+ fs.mkdirSync(dst, { recursive: true });
218
+ const entries = fs.readdirSync(src, { withFileTypes: true });
219
+ for (const entry of entries) {
220
+ const srcPath = path.join(src, entry.name);
221
+ const dstPath = path.join(dst, entry.name);
222
+ if (entry.isDirectory()) {
223
+ copyDirSync(srcPath, dstPath);
224
+ } else {
225
+ fs.copyFileSync(srcPath, dstPath);
226
+ }
227
+ }
228
+ }
229
+
230
+ module.exports = run;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axiomatic-labs/claudeflow",
3
- "version": "2.4.0",
3
+ "version": "2.4.1",
4
4
  "description": "Claudeflow — AI-powered development toolkit for Claude Code. Skills, agents, hooks, and quality gates that ship production apps.",
5
5
  "bin": {
6
6
  "claudeflow": "./bin/cli.js"
package/lib/init.js DELETED
@@ -1,138 +0,0 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const { execSync } = require('child_process');
4
- const { requireAuth } = require('./auth.js');
5
- const { getLatestRelease, downloadReleaseAsset } = require('./download.js');
6
- const { writeLocalVersion } = require('./version.js');
7
- const ui = require('./ui.js');
8
-
9
- async function run() {
10
- // Check if already initialized
11
- const versionFile = path.join(process.cwd(), '.claude', '.claudeflow-version');
12
- if (fs.existsSync(versionFile)) {
13
- const current = fs.readFileSync(versionFile, 'utf-8').trim();
14
- ui.banner(current);
15
- ui.warn(`Already initialized at ${current}.`);
16
- ui.info('Run: claudeflow update');
17
- console.log('');
18
- process.exit(1);
19
- }
20
-
21
- ui.banner();
22
-
23
- ui.step('Authenticating with GitHub...');
24
- const token = await requireAuth();
25
-
26
- ui.step('Fetching latest release...');
27
- const release = await getLatestRelease(token);
28
- const version = release.tag_name;
29
-
30
- // Find the ZIP asset
31
- const asset = release.assets.find((a) => a.name.endsWith('.zip'));
32
- if (!asset) {
33
- ui.error('No ZIP asset found in the latest release.');
34
- process.exit(1);
35
- }
36
-
37
- ui.step(`Downloading ${version}...`);
38
- const zipBuffer = await downloadReleaseAsset(asset, token);
39
-
40
- // Write ZIP to temp file and extract
41
- const tmpZip = path.join(require('os').tmpdir(), `claudeflow-${Date.now()}.zip`);
42
- fs.writeFileSync(tmpZip, zipBuffer);
43
-
44
- try {
45
- ui.step('Extracting template files...');
46
- execSync(`unzip -o "${tmpZip}" -d "${process.cwd()}"`, { stdio: 'pipe' });
47
- } finally {
48
- fs.unlinkSync(tmpZip);
49
- }
50
-
51
- // Ensure .claude/tmp exists
52
- fs.mkdirSync(path.join(process.cwd(), '.claude', 'tmp'), { recursive: true });
53
-
54
- // Write version file
55
- writeLocalVersion(version);
56
-
57
- // Count installed items
58
- const skillsDir = path.join(process.cwd(), '.claude', 'skills');
59
- let skillCount = 0;
60
- try {
61
- skillCount = fs.readdirSync(skillsDir, { withFileTypes: true }).filter((e) => e.isDirectory()).length;
62
- } catch {}
63
-
64
- const hooksDir = path.join(process.cwd(), '.claude', 'hooks');
65
- let hookCount = 0;
66
- try {
67
- hookCount = fs.readdirSync(hooksDir, { withFileTypes: true }).filter((e) => e.isDirectory()).length;
68
- } catch {}
69
-
70
- const docsDir = path.join(process.cwd(), '.claude', 'docs');
71
- let docCount = 0;
72
- try {
73
- docCount = fs.readdirSync(docsDir).filter((f) => !f.startsWith('.')).length;
74
- } catch {}
75
-
76
- console.log('');
77
- ui.success(`${skillCount} skills installed`);
78
- ui.success(`${hookCount} hooks installed`);
79
- ui.success(`${docCount} docs installed`);
80
-
81
- ui.done(`Claudeflow ${version} installed.`);
82
-
83
- // Detect project type: greenfield, single-stack, or multi-stack
84
- const MANIFESTS = ['package.json', 'pyproject.toml', 'Gemfile', 'go.mod', 'Cargo.toml', 'composer.json'];
85
- const SKIP_DIRS = new Set(['node_modules', 'vendor', '__pycache__', 'dist', 'build', '.next', '.nuxt', '.output', '.claude']);
86
-
87
- const rootHasManifest = MANIFESTS.some((f) => fs.existsSync(path.join(process.cwd(), f)));
88
-
89
- // Scan first-level subdirs for manifests (multi-stack detection)
90
- const stackDirs = [];
91
- try {
92
- const entries = fs.readdirSync(process.cwd(), { withFileTypes: true });
93
- for (const entry of entries) {
94
- if (!entry.isDirectory() || entry.name.startsWith('.') || SKIP_DIRS.has(entry.name)) continue;
95
- if (MANIFESTS.some((f) => fs.existsSync(path.join(process.cwd(), entry.name, f)))) {
96
- stackDirs.push(entry.name);
97
- }
98
- }
99
- } catch {}
100
-
101
- const isMultiStack = stackDirs.length > 1 || (rootHasManifest && stackDirs.length > 0);
102
- const isGreenfield = !rootHasManifest && stackDirs.length === 0;
103
-
104
- console.log(` ${ui.BOLD}Getting started:${ui.RESET}`);
105
- console.log('');
106
- console.log(` 1. Run ${ui.CYAN}claude${ui.RESET} to start Claude Code`);
107
-
108
- if (isGreenfield) {
109
- console.log(` 2. Type ${ui.CYAN}/claudeflow-init${ui.RESET} to scaffold your project`);
110
- console.log('');
111
- console.log(` ${ui.DIM}Claudeflow will ask what you want to build, research${ui.RESET}`);
112
- console.log(` ${ui.DIM}the best stack, and generate project-specific skills.${ui.RESET}`);
113
- } else if (isMultiStack) {
114
- console.log(` 2. Type ${ui.CYAN}/claudeflow-update${ui.RESET} to detect your stacks and generate project-specific skills`);
115
- console.log('');
116
- ui.info(`Multi-stack project detected: ${stackDirs.join(', ')}${rootHasManifest ? ' + root' : ''}`);
117
- console.log('');
118
- console.log(` ${ui.BOLD}After setup:${ui.RESET}`);
119
- console.log(` ${ui.DIM}Just describe what you want — Claudeflow handles the rest.${ui.RESET}`);
120
- } else {
121
- // Single-stack existing project
122
- console.log(` 2. Type ${ui.CYAN}/claudeflow-update${ui.RESET} to detect your stack and generate project-specific skills`);
123
- console.log(` 3. Tell Claude what you want — it handles the rest`);
124
- }
125
-
126
- console.log('');
127
- console.log(` ${ui.BOLD}Commands:${ui.RESET}`);
128
- console.log(` ${ui.CYAN}/claudeflow-init${ui.RESET} ${ui.DIM}Scaffold a new project (greenfield)${ui.RESET}`);
129
- console.log(` ${ui.CYAN}/claudeflow-update${ui.RESET} ${ui.DIM}Detect stack and generate skills for existing project${ui.RESET}`);
130
- console.log(` ${ui.CYAN}/claudeflow-design-tokens${ui.RESET} ${ui.DIM}Create design reference from a site or screenshot${ui.RESET}`);
131
- console.log(` ${ui.CYAN}/claudeflow-create-ui${ui.RESET} ${ui.DIM}Build pages from visual references${ui.RESET}`);
132
- console.log(` ${ui.CYAN}/claudeflow-checkpoints${ui.RESET} ${ui.DIM}Auto-save work with git checkpoints${ui.RESET}`);
133
- console.log('');
134
- console.log(` ${ui.DIM}Docs: https://claudeflow.dev${ui.RESET}`);
135
- console.log('');
136
- }
137
-
138
- module.exports = run;
package/lib/update.js DELETED
@@ -1,160 +0,0 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const { execSync } = require('child_process');
4
- const { requireAuth } = require('./auth.js');
5
- const { getLatestRelease, downloadReleaseAsset } = require('./download.js');
6
- const { writeLocalVersion, readLocalVersion } = require('./version.js');
7
- const ui = require('./ui.js');
8
-
9
- // Template skills that ship with the ZIP — only these get overwritten.
10
- // User-generated skills (from /setup, /update) are preserved.
11
- const TEMPLATE_SKILLS = new Set([
12
- 'claudeflow-init',
13
- 'claudeflow-update',
14
- 'claudeflow-design-tokens',
15
- 'claudeflow-create-ui',
16
- 'claudeflow-checkpoints',
17
- ]);
18
-
19
- async function run() {
20
- const current = readLocalVersion();
21
-
22
- ui.banner(current);
23
-
24
- if (!current) {
25
- ui.warn('Claudeflow is not installed. Run: npx @axiomatic-labs/claudeflow');
26
- console.log('');
27
- process.exit(1);
28
- }
29
-
30
- ui.step('Authenticating with GitHub...');
31
- const token = await requireAuth();
32
-
33
- ui.step('Fetching latest release...');
34
- const release = await getLatestRelease(token);
35
- const version = release.tag_name;
36
-
37
- // Find the ZIP asset
38
- const asset = release.assets.find((a) => a.name.endsWith('.zip'));
39
- if (!asset) {
40
- ui.error('No ZIP asset found in the latest release.');
41
- process.exit(1);
42
- }
43
-
44
- ui.step(`Downloading ${version}...`);
45
- const zipBuffer = await downloadReleaseAsset(asset, token);
46
-
47
- // Write ZIP to temp file and extract to a temp directory first
48
- const tmpZip = path.join(require('os').tmpdir(), `claudeflow-update-${Date.now()}.zip`);
49
- const tmpExtract = path.join(require('os').tmpdir(), `claudeflow-extract-${Date.now()}`);
50
- fs.writeFileSync(tmpZip, zipBuffer);
51
- fs.mkdirSync(tmpExtract, { recursive: true });
52
-
53
- try {
54
- ui.step('Extracting template files...');
55
- execSync(`unzip -o "${tmpZip}" -d "${tmpExtract}"`, { stdio: 'pipe' });
56
-
57
- const cwd = process.cwd();
58
- const srcClaude = path.join(tmpExtract, '.claude');
59
-
60
- // Copy settings.json
61
- const srcSettings = path.join(srcClaude, 'settings.json');
62
- const dstSettings = path.join(cwd, '.claude', 'settings.json');
63
- if (fs.existsSync(srcSettings)) {
64
- fs.copyFileSync(srcSettings, dstSettings);
65
- }
66
-
67
- // Copy template skills only (preserve user-generated skills)
68
- const srcSkills = path.join(srcClaude, 'skills');
69
- const dstSkills = path.join(cwd, '.claude', 'skills');
70
- if (fs.existsSync(srcSkills)) {
71
- fs.mkdirSync(dstSkills, { recursive: true });
72
- const skillDirs = fs.readdirSync(srcSkills, { withFileTypes: true });
73
- for (const entry of skillDirs) {
74
- if (entry.isDirectory() && TEMPLATE_SKILLS.has(entry.name)) {
75
- copyDirSync(path.join(srcSkills, entry.name), path.join(dstSkills, entry.name));
76
- }
77
- }
78
- }
79
-
80
- // Copy all hooks
81
- const srcHooks = path.join(srcClaude, 'hooks');
82
- const dstHooks = path.join(cwd, '.claude', 'hooks');
83
- if (fs.existsSync(srcHooks)) {
84
- copyDirSync(srcHooks, dstHooks);
85
- }
86
-
87
- // Copy all docs
88
- const srcDocs = path.join(srcClaude, 'docs');
89
- const dstDocs = path.join(cwd, '.claude', 'docs');
90
- if (fs.existsSync(srcDocs)) {
91
- copyDirSync(srcDocs, dstDocs);
92
- }
93
-
94
- // Ensure .claude/tmp exists
95
- fs.mkdirSync(path.join(cwd, '.claude', 'tmp'), { recursive: true });
96
-
97
- // NOTE: CLAUDE.md is NOT overwritten — it gets generated dynamically by /setup and /update skills
98
-
99
- // Copy .mcp.json to project root (empty — no MCP server needed)
100
- const srcMcp = path.join(tmpExtract, '.mcp.json');
101
- const dstMcp = path.join(cwd, '.mcp.json');
102
- if (fs.existsSync(srcMcp)) {
103
- fs.copyFileSync(srcMcp, dstMcp);
104
- }
105
-
106
- // Write version file
107
- writeLocalVersion(version);
108
-
109
- } finally {
110
- fs.unlinkSync(tmpZip);
111
- fs.rmSync(tmpExtract, { recursive: true, force: true });
112
- }
113
-
114
- // Count updated items
115
- const skillsDir = path.join(process.cwd(), '.claude', 'skills');
116
- let skillCount = 0;
117
- try {
118
- skillCount = fs.readdirSync(skillsDir, { withFileTypes: true }).filter((e) => e.isDirectory()).length;
119
- } catch {}
120
-
121
- const hooksDir = path.join(process.cwd(), '.claude', 'hooks');
122
- let hookCount = 0;
123
- try {
124
- hookCount = fs.readdirSync(hooksDir, { withFileTypes: true }).filter((e) => e.isDirectory()).length;
125
- } catch {}
126
-
127
- const docsDir = path.join(process.cwd(), '.claude', 'docs');
128
- let docCount = 0;
129
- try {
130
- docCount = fs.readdirSync(docsDir).filter((f) => !f.startsWith('.')).length;
131
- } catch {}
132
-
133
- console.log('');
134
- ui.success(`${skillCount} skills (${TEMPLATE_SKILLS.size} template updated, user skills preserved)`);
135
- ui.success(`${hookCount} hooks updated`);
136
- ui.success(`${docCount} docs updated`);
137
-
138
- if (current === version) {
139
- ui.done(`Claudeflow ${version} reinstalled.`);
140
- } else {
141
- ui.done(`Claudeflow updated: ${current} → ${version}`);
142
- }
143
- }
144
-
145
- // Recursively copy a directory, creating parents as needed
146
- function copyDirSync(src, dst) {
147
- fs.mkdirSync(dst, { recursive: true });
148
- const entries = fs.readdirSync(src, { withFileTypes: true });
149
- for (const entry of entries) {
150
- const srcPath = path.join(src, entry.name);
151
- const dstPath = path.join(dst, entry.name);
152
- if (entry.isDirectory()) {
153
- copyDirSync(srcPath, dstPath);
154
- } else {
155
- fs.copyFileSync(srcPath, dstPath);
156
- }
157
- }
158
- }
159
-
160
- module.exports = run;