@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 +9 -12
- package/lib/install.js +230 -0
- package/package.json +1 -1
- package/lib/init.js +0 -138
- package/lib/update.js +0 -160
package/bin/cli.js
CHANGED
|
@@ -1,24 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// npx entry point: handles install
|
|
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] || '
|
|
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
|
|
21
|
-
await
|
|
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}
|
|
38
|
-
console.log(` ${ui.CYAN}update${ui.RESET}
|
|
39
|
-
console.log(` ${ui.CYAN}version${ui.RESET}
|
|
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.
|
|
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;
|