@axiomatic-labs/claudeflow 2.3.8 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +35 -153
- package/lib/init.js +1 -1
- package/lib/update.js +160 -0
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -1,164 +1,46 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
//
|
|
3
|
+
// npx entry point: handles install, update, and version commands directly.
|
|
4
|
+
// No Go binary — downloads ZIP and extracts skill files to disk.
|
|
4
5
|
|
|
5
|
-
const { spawn } = require('child_process');
|
|
6
6
|
const path = require('path');
|
|
7
|
-
const fs = require('fs');
|
|
8
|
-
const os = require('os');
|
|
9
|
-
const https = require('https');
|
|
10
|
-
|
|
11
|
-
const OWNER = 'axiomatic-labs';
|
|
12
|
-
const REPO = 'claudeflow-cli';
|
|
13
|
-
const BIN_DIR = path.join(os.homedir(), '.claudeflow', 'bin');
|
|
14
|
-
const isWindows = os.platform() === 'win32';
|
|
15
|
-
const BIN_PATH = path.join(BIN_DIR, isWindows ? 'claudeflow.exe' : 'claudeflow');
|
|
16
|
-
const VERSION_PATH = path.join(os.homedir(), '.claudeflow', 'binary-version');
|
|
17
|
-
|
|
18
|
-
// Detect platform: darwin-arm64, darwin-amd64, linux-amd64, windows-amd64
|
|
19
|
-
function getPlatformBinary() {
|
|
20
|
-
const platform = os.platform(); // darwin, linux, win32
|
|
21
|
-
const arch = os.arch(); // arm64, x64
|
|
22
|
-
const goArch = arch === 'x64' ? 'amd64' : arch;
|
|
23
|
-
const goPlatform = platform === 'win32' ? 'windows' : platform;
|
|
24
|
-
const ext = platform === 'win32' ? '.exe' : '';
|
|
25
|
-
return `claudeflow-${goPlatform}-${goArch}${ext}`;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function fetchUrl(url, headers = {}) {
|
|
29
|
-
return new Promise((resolve, reject) => {
|
|
30
|
-
const mod = url.startsWith('https') ? https : require('http');
|
|
31
|
-
mod.get(url, { headers: { 'User-Agent': 'claudeflow-cli', ...headers } }, (res) => {
|
|
32
|
-
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
33
|
-
return fetchUrl(res.headers.location, {}).then(resolve).catch(reject);
|
|
34
|
-
}
|
|
35
|
-
if (res.statusCode !== 200) {
|
|
36
|
-
let body = '';
|
|
37
|
-
res.on('data', (d) => body += d);
|
|
38
|
-
res.on('end', () => reject(new Error(`HTTP ${res.statusCode}: ${body.slice(0, 200)}`)));
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
const chunks = [];
|
|
42
|
-
res.on('data', (d) => chunks.push(d));
|
|
43
|
-
res.on('end', () => resolve(Buffer.concat(chunks)));
|
|
44
|
-
}).on('error', reject);
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async function downloadBinary(token) {
|
|
49
|
-
const ui = require('../lib/ui.js');
|
|
50
|
-
|
|
51
|
-
ui.step('Fetching latest release...');
|
|
52
|
-
|
|
53
|
-
const releaseData = await fetchUrl(
|
|
54
|
-
`https://api.github.com/repos/${OWNER}/${REPO}/releases/latest`,
|
|
55
|
-
{ Authorization: `Bearer ${token}`, Accept: 'application/vnd.github+json' }
|
|
56
|
-
);
|
|
57
|
-
const release = JSON.parse(releaseData.toString());
|
|
58
|
-
|
|
59
|
-
const binaryName = getPlatformBinary();
|
|
60
|
-
const asset = release.assets.find((a) => a.name === binaryName);
|
|
61
|
-
if (!asset) {
|
|
62
|
-
ui.error(`No binary found for your platform: ${binaryName}`);
|
|
63
|
-
ui.info(`Available: ${release.assets.map((a) => a.name).join(', ')}`);
|
|
64
|
-
process.exit(1);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
ui.step(`Downloading ${release.tag_name} (${binaryName})...`);
|
|
68
|
-
|
|
69
|
-
const binaryData = await fetchUrl(
|
|
70
|
-
`https://api.github.com/repos/${OWNER}/${REPO}/releases/assets/${asset.id}`,
|
|
71
|
-
{ Authorization: `Bearer ${token}`, Accept: 'application/octet-stream' }
|
|
72
|
-
);
|
|
73
|
-
|
|
74
|
-
fs.mkdirSync(BIN_DIR, { recursive: true });
|
|
75
|
-
fs.writeFileSync(BIN_PATH, binaryData, { mode: 0o755 });
|
|
76
|
-
|
|
77
|
-
// Ad-hoc sign on macOS (required by recent macOS versions)
|
|
78
|
-
if (os.platform() === 'darwin') {
|
|
79
|
-
const { execSync } = require('child_process');
|
|
80
|
-
try {
|
|
81
|
-
execSync(`codesign --force --sign - "${BIN_PATH}"`, { stdio: 'pipe' });
|
|
82
|
-
} catch {}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
fs.writeFileSync(VERSION_PATH, release.tag_name + '\n');
|
|
86
|
-
|
|
87
|
-
ui.success(`Claudeflow ${release.tag_name} installed to ~/.claudeflow/bin/`);
|
|
88
|
-
return release.tag_name;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
async function ensureBinary() {
|
|
92
|
-
if (!fs.existsSync(BIN_PATH)) {
|
|
93
|
-
// First run — interactive auth + download
|
|
94
|
-
const ui = require('../lib/ui.js');
|
|
95
|
-
const { requireAuth } = require('../lib/auth.js');
|
|
96
|
-
ui.banner();
|
|
97
|
-
ui.step('First run — downloading Claudeflow binary...');
|
|
98
|
-
|
|
99
|
-
const token = await requireAuth();
|
|
100
|
-
try {
|
|
101
|
-
await downloadBinary(token);
|
|
102
|
-
} catch (err) {
|
|
103
|
-
ui.error('Could not download Claudeflow binary.');
|
|
104
|
-
ui.info('Ensure your token has the "repo" scope and can access axiomatic-labs/claudeflow-cli.');
|
|
105
|
-
process.exit(1);
|
|
106
|
-
}
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Binary exists — check for updates silently
|
|
111
|
-
try {
|
|
112
|
-
const { getGitHubToken } = require('../lib/auth.js');
|
|
113
|
-
const token = getGitHubToken();
|
|
114
|
-
if (!token) return; // No cached token, skip check
|
|
115
|
-
|
|
116
|
-
const installed = fs.existsSync(VERSION_PATH)
|
|
117
|
-
? fs.readFileSync(VERSION_PATH, 'utf-8').trim()
|
|
118
|
-
: null;
|
|
119
|
-
|
|
120
|
-
const releaseData = await fetchUrl(
|
|
121
|
-
`https://api.github.com/repos/${OWNER}/${REPO}/releases/latest`,
|
|
122
|
-
{ Authorization: `Bearer ${token}`, Accept: 'application/vnd.github+json' }
|
|
123
|
-
);
|
|
124
|
-
const release = JSON.parse(releaseData.toString());
|
|
125
|
-
const latest = release.tag_name;
|
|
126
|
-
|
|
127
|
-
if (installed && installed === latest) return; // Already up to date
|
|
128
|
-
|
|
129
|
-
const ui = require('../lib/ui.js');
|
|
130
|
-
ui.banner();
|
|
131
|
-
ui.step(`Updating binary to ${latest}...`);
|
|
132
|
-
await downloadBinary(token);
|
|
133
|
-
} catch {
|
|
134
|
-
// Network/API error — skip silently, use existing binary
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
7
|
|
|
138
8
|
async function main() {
|
|
139
9
|
const args = process.argv.slice(2);
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
child.on('exit', (code) => {
|
|
151
|
-
process.exit(code || 0);
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
child.on('error', (err) => {
|
|
155
|
-
if (err.code === 'ENOENT') {
|
|
156
|
-
console.error('Claudeflow binary not found. Try reinstalling: npx @axiomatic-labs/claudeflow');
|
|
157
|
-
} else {
|
|
158
|
-
console.error(`Failed to start claudeflow: ${err.message}`);
|
|
10
|
+
const command = args[0] || 'install';
|
|
11
|
+
|
|
12
|
+
switch (command) {
|
|
13
|
+
case 'install':
|
|
14
|
+
case 'init': {
|
|
15
|
+
const init = require('../lib/init.js');
|
|
16
|
+
await init();
|
|
17
|
+
break;
|
|
159
18
|
}
|
|
160
|
-
|
|
161
|
-
|
|
19
|
+
case 'update': {
|
|
20
|
+
const update = require('../lib/update.js');
|
|
21
|
+
await update();
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
case 'version':
|
|
25
|
+
case '--version':
|
|
26
|
+
case '-v': {
|
|
27
|
+
const version = require('../lib/version.js');
|
|
28
|
+
await version();
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
default: {
|
|
32
|
+
const ui = require('../lib/ui.js');
|
|
33
|
+
ui.banner();
|
|
34
|
+
console.log(' Usage: npx @axiomatic-labs/claudeflow [command]');
|
|
35
|
+
console.log('');
|
|
36
|
+
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`);
|
|
40
|
+
console.log('');
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
162
44
|
}
|
|
163
45
|
|
|
164
46
|
main().catch((err) => {
|
package/lib/init.js
CHANGED
|
@@ -43,7 +43,7 @@ async function run() {
|
|
|
43
43
|
|
|
44
44
|
try {
|
|
45
45
|
ui.step('Extracting template files...');
|
|
46
|
-
execSync(`unzip -o "${tmpZip}"
|
|
46
|
+
execSync(`unzip -o "${tmpZip}" -d "${process.cwd()}"`, { stdio: 'pipe' });
|
|
47
47
|
} finally {
|
|
48
48
|
fs.unlinkSync(tmpZip);
|
|
49
49
|
}
|
package/lib/update.js
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
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;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axiomatic-labs/claudeflow",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
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"
|