@axiomatic-labs/claudeflow 2.0.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 +39 -0
- package/lib/auth.js +32 -0
- package/lib/download.js +99 -0
- package/lib/init.js +136 -0
- package/lib/manifest.js +38 -0
- package/lib/ui.js +53 -0
- package/lib/update.js +101 -0
- package/lib/version.js +63 -0
- package/package.json +40 -0
package/bin/cli.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const command = process.argv[2];
|
|
4
|
+
|
|
5
|
+
switch (command) {
|
|
6
|
+
case 'init':
|
|
7
|
+
require('../lib/init.js')();
|
|
8
|
+
break;
|
|
9
|
+
case 'update':
|
|
10
|
+
require('../lib/update.js')();
|
|
11
|
+
break;
|
|
12
|
+
case 'version':
|
|
13
|
+
require('../lib/version.js')();
|
|
14
|
+
break;
|
|
15
|
+
case '--version':
|
|
16
|
+
case '-v':
|
|
17
|
+
console.log(`claudeflow v${require('../package.json').version}`);
|
|
18
|
+
break;
|
|
19
|
+
case '--help':
|
|
20
|
+
case '-h':
|
|
21
|
+
case undefined: {
|
|
22
|
+
const ui = require('../lib/ui.js');
|
|
23
|
+
ui.banner();
|
|
24
|
+
console.log(` ${ui.BOLD}Usage:${ui.RESET}`);
|
|
25
|
+
console.log(` claudeflow ${ui.CYAN}init${ui.RESET} Install the latest template files`);
|
|
26
|
+
console.log(` claudeflow ${ui.CYAN}update${ui.RESET} Update templates to latest version`);
|
|
27
|
+
console.log(` claudeflow ${ui.CYAN}version${ui.RESET} Show version info`);
|
|
28
|
+
console.log('');
|
|
29
|
+
console.log(` ${ui.BOLD}Options:${ui.RESET}`);
|
|
30
|
+
console.log(` --version, -v Show CLI version`);
|
|
31
|
+
console.log(` --help, -h Show this help`);
|
|
32
|
+
console.log('');
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
default:
|
|
36
|
+
console.error(`Unknown command: ${command}`);
|
|
37
|
+
console.error('Run "claudeflow --help" for usage.');
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
package/lib/auth.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const { execSync } = require('child_process');
|
|
2
|
+
|
|
3
|
+
function getGitHubToken() {
|
|
4
|
+
// Try gh CLI first
|
|
5
|
+
try {
|
|
6
|
+
const token = execSync('gh auth token', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
7
|
+
if (token) return token;
|
|
8
|
+
} catch {}
|
|
9
|
+
|
|
10
|
+
// Fall back to GITHUB_TOKEN env var
|
|
11
|
+
if (process.env.GITHUB_TOKEN) {
|
|
12
|
+
return process.env.GITHUB_TOKEN;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function requireAuth() {
|
|
19
|
+
const token = getGitHubToken();
|
|
20
|
+
if (!token) {
|
|
21
|
+
console.error('GitHub authentication required.\n');
|
|
22
|
+
console.error('Option 1: Install the GitHub CLI and authenticate:');
|
|
23
|
+
console.error(' brew install gh && gh auth login\n');
|
|
24
|
+
console.error('Option 2: Set a GitHub Personal Access Token:');
|
|
25
|
+
console.error(' export GITHUB_TOKEN=ghp_your_token_here\n');
|
|
26
|
+
console.error('The token needs read access to axiomatic-labs/axiomatic-cli releases.');
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
return token;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports = { getGitHubToken, requireAuth };
|
package/lib/download.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
const https = require('https');
|
|
2
|
+
const { execSync } = require('child_process');
|
|
3
|
+
|
|
4
|
+
const OWNER = 'axiomatic-labs';
|
|
5
|
+
const REPO = 'axiomatic-cli';
|
|
6
|
+
|
|
7
|
+
function githubApi(path, token) {
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
const options = {
|
|
10
|
+
hostname: 'api.github.com',
|
|
11
|
+
path,
|
|
12
|
+
headers: {
|
|
13
|
+
'User-Agent': 'claudeflow-cli',
|
|
14
|
+
'Authorization': `Bearer ${token}`,
|
|
15
|
+
'Accept': 'application/vnd.github+json',
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
https.get(options, (res) => {
|
|
20
|
+
// Follow redirects
|
|
21
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
22
|
+
return fetchUrl(res.headers.location).then(resolve).catch(reject);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (res.statusCode !== 200) {
|
|
26
|
+
let body = '';
|
|
27
|
+
res.on('data', (d) => body += d);
|
|
28
|
+
res.on('end', () => reject(new Error(`GitHub API ${res.statusCode}: ${body}`)));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const chunks = [];
|
|
33
|
+
res.on('data', (d) => chunks.push(d));
|
|
34
|
+
res.on('end', () => {
|
|
35
|
+
try {
|
|
36
|
+
resolve(JSON.parse(Buffer.concat(chunks).toString()));
|
|
37
|
+
} catch (e) {
|
|
38
|
+
reject(e);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}).on('error', reject);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function fetchUrl(url) {
|
|
46
|
+
return new Promise((resolve, reject) => {
|
|
47
|
+
const mod = url.startsWith('https') ? https : require('http');
|
|
48
|
+
mod.get(url, { headers: { 'User-Agent': 'claudeflow-cli' } }, (res) => {
|
|
49
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
50
|
+
return fetchUrl(res.headers.location).then(resolve).catch(reject);
|
|
51
|
+
}
|
|
52
|
+
if (res.statusCode !== 200) {
|
|
53
|
+
return reject(new Error(`HTTP ${res.statusCode} fetching ${url}`));
|
|
54
|
+
}
|
|
55
|
+
const chunks = [];
|
|
56
|
+
res.on('data', (d) => chunks.push(d));
|
|
57
|
+
res.on('end', () => resolve(Buffer.concat(chunks)));
|
|
58
|
+
}).on('error', reject);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function getLatestRelease(token) {
|
|
63
|
+
return githubApi(`/repos/${OWNER}/${REPO}/releases/latest`, token);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function downloadReleaseAsset(asset, token) {
|
|
67
|
+
// Use the GitHub API URL with Accept header for binary
|
|
68
|
+
return new Promise((resolve, reject) => {
|
|
69
|
+
const options = {
|
|
70
|
+
hostname: 'api.github.com',
|
|
71
|
+
path: `/repos/${OWNER}/${REPO}/releases/assets/${asset.id}`,
|
|
72
|
+
headers: {
|
|
73
|
+
'User-Agent': 'claudeflow-cli',
|
|
74
|
+
'Authorization': `Bearer ${token}`,
|
|
75
|
+
'Accept': 'application/octet-stream',
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
https.get(options, (res) => {
|
|
80
|
+
// GitHub redirects to a CDN URL for the actual binary
|
|
81
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
82
|
+
return fetchUrl(res.headers.location).then(resolve).catch(reject);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (res.statusCode !== 200) {
|
|
86
|
+
let body = '';
|
|
87
|
+
res.on('data', (d) => body += d);
|
|
88
|
+
res.on('end', () => reject(new Error(`Asset download failed ${res.statusCode}: ${body}`)));
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const chunks = [];
|
|
93
|
+
res.on('data', (d) => chunks.push(d));
|
|
94
|
+
res.on('end', () => resolve(Buffer.concat(chunks)));
|
|
95
|
+
}).on('error', reject);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
module.exports = { getLatestRelease, downloadReleaseAsset, OWNER, REPO };
|
package/lib/init.js
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
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 = 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}" ".claude/*" "CLAUDE.md" -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}/setup${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}/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}/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}/setup${ui.RESET} ${ui.DIM}Scaffold a new project (greenfield)${ui.RESET}`);
|
|
129
|
+
console.log(` ${ui.CYAN}/update${ui.RESET} ${ui.DIM}Detect stack and generate skills for existing project${ui.RESET}`);
|
|
130
|
+
console.log(` ${ui.CYAN}/sdd${ui.RESET} ${ui.DIM}Generate a feature spec (Spec-Driven Development)${ui.RESET}`);
|
|
131
|
+
console.log('');
|
|
132
|
+
console.log(` ${ui.DIM}Docs: https://claudeflow.dev${ui.RESET}`);
|
|
133
|
+
console.log('');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
module.exports = run;
|
package/lib/manifest.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Canonical list of template-managed files — mirrors build-zip.sh.
|
|
2
|
+
// Only these files are managed by init/update. Everything else is user-generated.
|
|
3
|
+
|
|
4
|
+
// Directory prefixes: any file under these paths is template-managed.
|
|
5
|
+
const TEMPLATE_PREFIXES = [
|
|
6
|
+
'.claude/skills/claudeflow/',
|
|
7
|
+
'.claude/skills/claudeflow-init/',
|
|
8
|
+
'.claude/skills/claudeflow-update/',
|
|
9
|
+
'.claude/skills/sdd/',
|
|
10
|
+
'.claude/docs/',
|
|
11
|
+
'.claude/hooks/',
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
// Exact file paths that are template-managed.
|
|
15
|
+
const TEMPLATE_FILES = [
|
|
16
|
+
'CLAUDE.md',
|
|
17
|
+
'.claude/settings.json',
|
|
18
|
+
'.claude/agents/example-agent.md',
|
|
19
|
+
'.claude/rules/example-rules.md',
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
// Check if a path from the ZIP is a template file.
|
|
23
|
+
function isTemplatePath(relativePath) {
|
|
24
|
+
// Exact match
|
|
25
|
+
if (TEMPLATE_FILES.includes(relativePath)) return true;
|
|
26
|
+
|
|
27
|
+
// Prefix match (directory-based)
|
|
28
|
+
for (const prefix of TEMPLATE_PREFIXES) {
|
|
29
|
+
if (relativePath.startsWith(prefix)) return true;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// .claudeflow-version is also managed
|
|
33
|
+
if (relativePath === '.claude/.claudeflow-version') return true;
|
|
34
|
+
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = { TEMPLATE_PREFIXES, TEMPLATE_FILES, isTemplatePath };
|
package/lib/ui.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Branding and UI helpers for the Claudeflow CLI.
|
|
2
|
+
|
|
3
|
+
const RESET = '\x1b[0m';
|
|
4
|
+
const BOLD = '\x1b[1m';
|
|
5
|
+
const DIM = '\x1b[2m';
|
|
6
|
+
const CYAN = '\x1b[36m';
|
|
7
|
+
const GREEN = '\x1b[32m';
|
|
8
|
+
const YELLOW = '\x1b[33m';
|
|
9
|
+
const RED = '\x1b[31m';
|
|
10
|
+
const WHITE = '\x1b[97m';
|
|
11
|
+
const MAGENTA = '\x1b[35m';
|
|
12
|
+
|
|
13
|
+
function banner(version) {
|
|
14
|
+
console.log('');
|
|
15
|
+
console.log(`${DIM} ╔═══════════════════════════════════════╗${RESET}`);
|
|
16
|
+
console.log(`${DIM} ║ ║${RESET}`);
|
|
17
|
+
console.log(`${DIM} ║${RESET} ${MAGENTA}${BOLD}◆${RESET} ${WHITE}${BOLD}C L A U D E F L O W${RESET} ${DIM}║${RESET}`);
|
|
18
|
+
console.log(`${DIM} ║${RESET} ${DIM}AI-Powered Development${RESET} ${DIM}║${RESET}`);
|
|
19
|
+
console.log(`${DIM} ║ ║${RESET}`);
|
|
20
|
+
console.log(`${DIM} ╚═══════════════════════════════════════╝${RESET}`);
|
|
21
|
+
if (version) {
|
|
22
|
+
console.log(`${DIM} ${version}${RESET}`);
|
|
23
|
+
}
|
|
24
|
+
console.log('');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function step(msg) {
|
|
28
|
+
console.log(` ${DIM}...${RESET} ${msg}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function success(msg) {
|
|
32
|
+
console.log(` ${GREEN}✓${RESET} ${msg}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function warn(msg) {
|
|
36
|
+
console.log(` ${YELLOW}!${RESET} ${msg}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function error(msg) {
|
|
40
|
+
console.error(` ${RED}✗${RESET} ${msg}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function info(msg) {
|
|
44
|
+
console.log(` ${CYAN}i${RESET} ${msg}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function done(msg) {
|
|
48
|
+
console.log('');
|
|
49
|
+
console.log(` ${GREEN}${BOLD}${msg}${RESET}`);
|
|
50
|
+
console.log('');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = { banner, step, success, warn, error, info, done, BOLD, DIM, RESET, GREEN, YELLOW, CYAN, RED, WHITE, MAGENTA };
|
package/lib/update.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
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 { readLocalVersion, writeLocalVersion } = require('./version.js');
|
|
7
|
+
const { isTemplatePath } = require('./manifest.js');
|
|
8
|
+
const ui = require('./ui.js');
|
|
9
|
+
|
|
10
|
+
async function run() {
|
|
11
|
+
const current = readLocalVersion();
|
|
12
|
+
if (!current) {
|
|
13
|
+
ui.banner();
|
|
14
|
+
ui.error('Not initialized. Run: claudeflow init');
|
|
15
|
+
console.log('');
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
ui.banner(current);
|
|
20
|
+
|
|
21
|
+
ui.step('Authenticating with GitHub...');
|
|
22
|
+
const token = requireAuth();
|
|
23
|
+
|
|
24
|
+
ui.step('Checking for updates...');
|
|
25
|
+
const release = await getLatestRelease(token);
|
|
26
|
+
const latest = release.tag_name;
|
|
27
|
+
|
|
28
|
+
if (current === latest) {
|
|
29
|
+
ui.success(`Already up to date (${current}).`);
|
|
30
|
+
console.log('');
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
ui.info(`${current} ${ui.DIM}->${ui.RESET} ${ui.BOLD}${latest}${ui.RESET}`);
|
|
35
|
+
|
|
36
|
+
// Find the ZIP asset
|
|
37
|
+
const asset = release.assets.find((a) => a.name.endsWith('.zip'));
|
|
38
|
+
if (!asset) {
|
|
39
|
+
ui.error('No ZIP asset found in the latest release.');
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
ui.step(`Downloading ${latest}...`);
|
|
44
|
+
const zipBuffer = await downloadReleaseAsset(asset, token);
|
|
45
|
+
|
|
46
|
+
// Write ZIP to temp file
|
|
47
|
+
const tmpZip = path.join(require('os').tmpdir(), `claudeflow-${Date.now()}.zip`);
|
|
48
|
+
const tmpDir = path.join(require('os').tmpdir(), `claudeflow-extract-${Date.now()}`);
|
|
49
|
+
fs.writeFileSync(tmpZip, zipBuffer);
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
// Extract to temp directory first
|
|
53
|
+
fs.mkdirSync(tmpDir, { recursive: true });
|
|
54
|
+
execSync(`unzip -o "${tmpZip}" -d "${tmpDir}"`, { stdio: 'pipe' });
|
|
55
|
+
|
|
56
|
+
ui.step('Updating template files...');
|
|
57
|
+
|
|
58
|
+
// Copy only template files
|
|
59
|
+
const updated = [];
|
|
60
|
+
copyTemplateFiles(tmpDir, process.cwd(), '', updated);
|
|
61
|
+
|
|
62
|
+
// Write version file
|
|
63
|
+
writeLocalVersion(latest);
|
|
64
|
+
|
|
65
|
+
console.log('');
|
|
66
|
+
const skills = updated.filter((f) => f.includes('/skills/')).length;
|
|
67
|
+
const hooks = updated.filter((f) => f.includes('/hooks/')).length;
|
|
68
|
+
const docs = updated.filter((f) => f.includes('/docs/')).length;
|
|
69
|
+
const other = updated.length - skills - hooks - docs;
|
|
70
|
+
|
|
71
|
+
if (skills) ui.success(`${skills} skill files updated`);
|
|
72
|
+
if (hooks) ui.success(`${hooks} hook files updated`);
|
|
73
|
+
if (docs) ui.success(`${docs} doc files updated`);
|
|
74
|
+
if (other) ui.success(`${other} other files updated`);
|
|
75
|
+
|
|
76
|
+
ui.done(`Updated to ${latest}.`);
|
|
77
|
+
ui.info('Generated skills/agents/rules are preserved.');
|
|
78
|
+
console.log('');
|
|
79
|
+
} finally {
|
|
80
|
+
fs.unlinkSync(tmpZip);
|
|
81
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function copyTemplateFiles(srcDir, destDir, prefix, updated) {
|
|
86
|
+
const entries = fs.readdirSync(path.join(srcDir, prefix), { withFileTypes: true });
|
|
87
|
+
for (const entry of entries) {
|
|
88
|
+
const relative = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
89
|
+
if (entry.isDirectory()) {
|
|
90
|
+
copyTemplateFiles(srcDir, destDir, relative, updated);
|
|
91
|
+
} else if (isTemplatePath(relative)) {
|
|
92
|
+
const src = path.join(srcDir, relative);
|
|
93
|
+
const dest = path.join(destDir, relative);
|
|
94
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
95
|
+
fs.copyFileSync(src, dest);
|
|
96
|
+
updated.push(relative);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
module.exports = run;
|
package/lib/version.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { getGitHubToken } = require('./auth.js');
|
|
4
|
+
const { getLatestRelease } = require('./download.js');
|
|
5
|
+
const ui = require('./ui.js');
|
|
6
|
+
|
|
7
|
+
const VERSION_FILE = path.join(process.cwd(), '.claude', '.claudeflow-version');
|
|
8
|
+
|
|
9
|
+
function readLocalVersion() {
|
|
10
|
+
try {
|
|
11
|
+
return fs.readFileSync(VERSION_FILE, 'utf-8').trim();
|
|
12
|
+
} catch {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function writeLocalVersion(version) {
|
|
18
|
+
const dir = path.dirname(VERSION_FILE);
|
|
19
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
20
|
+
fs.writeFileSync(VERSION_FILE, version + '\n');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function run() {
|
|
24
|
+
const local = readLocalVersion();
|
|
25
|
+
|
|
26
|
+
ui.banner(local);
|
|
27
|
+
|
|
28
|
+
const cliVersion = require('../package.json').version;
|
|
29
|
+
ui.info(`CLI version: v${cliVersion}`);
|
|
30
|
+
|
|
31
|
+
if (local) {
|
|
32
|
+
ui.info(`Installed: ${local}`);
|
|
33
|
+
} else {
|
|
34
|
+
ui.warn('Not installed. Run: claudeflow init');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const token = getGitHubToken();
|
|
38
|
+
if (!token) {
|
|
39
|
+
ui.warn('Authenticate with GitHub to check for updates.');
|
|
40
|
+
console.log('');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const release = await getLatestRelease(token);
|
|
46
|
+
const latest = release.tag_name;
|
|
47
|
+
ui.info(`Latest: ${latest}`);
|
|
48
|
+
|
|
49
|
+
console.log('');
|
|
50
|
+
if (local && local === latest) {
|
|
51
|
+
ui.success('Up to date.');
|
|
52
|
+
} else if (local) {
|
|
53
|
+
ui.warn(`Update available. Run: claudeflow update`);
|
|
54
|
+
}
|
|
55
|
+
} catch (err) {
|
|
56
|
+
ui.error(`Could not fetch latest release: ${err.message}`);
|
|
57
|
+
}
|
|
58
|
+
console.log('');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
module.exports = run;
|
|
62
|
+
module.exports.readLocalVersion = readLocalVersion;
|
|
63
|
+
module.exports.writeLocalVersion = writeLocalVersion;
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@axiomatic-labs/claudeflow",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Claudeflow — AI-powered development toolkit for Claude Code. Skills, agents, hooks, and quality gates that ship production apps.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"claudeflow": "./bin/cli.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"bin/",
|
|
10
|
+
"lib/",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"keywords": [
|
|
14
|
+
"claude",
|
|
15
|
+
"claude-code",
|
|
16
|
+
"ai",
|
|
17
|
+
"ai-agents",
|
|
18
|
+
"ai-coding",
|
|
19
|
+
"developer-tools",
|
|
20
|
+
"cli",
|
|
21
|
+
"scaffolding",
|
|
22
|
+
"code-quality",
|
|
23
|
+
"automation",
|
|
24
|
+
"skills",
|
|
25
|
+
"agents",
|
|
26
|
+
"anthropic",
|
|
27
|
+
"llm",
|
|
28
|
+
"devtools"
|
|
29
|
+
],
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=18"
|
|
32
|
+
},
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "https://github.com/axiomatic-labs/axiomatic-cli.git"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://claudeflow.dev",
|
|
38
|
+
"license": "UNLICENSED",
|
|
39
|
+
"private": false
|
|
40
|
+
}
|