@axiomatic-labs/claudeflow 2.3.9 → 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 CHANGED
@@ -1,164 +1,46 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // npm shim: downloads the Go binary if needed, then delegates all commands to it.
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
- // These commands need the Go binary
142
- await ensureBinary();
143
-
144
- // Delegate to Go binary with transparent I/O
145
- const child = spawn(BIN_PATH, args, {
146
- stdio: 'inherit',
147
- env: process.env,
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
- process.exit(1);
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}" ".claude/*" -d "${process.cwd()}"`, { stdio: 'pipe' });
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.9",
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"