@axiomatic-labs/cli 1.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 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(`@axiomatic-labs/cli v${require('../package.json').version}`);
18
+ break;
19
+ case '--help':
20
+ case '-h':
21
+ case undefined:
22
+ console.log(`
23
+ axiomatic - Install and update Axiomatic project templates
24
+
25
+ Usage:
26
+ axiomatic init Download and install the latest template files
27
+ axiomatic update Update template files to the latest version
28
+ axiomatic version Show local and latest version info
29
+
30
+ Options:
31
+ --version, -v Show CLI version
32
+ --help, -h Show this help message
33
+ `);
34
+ break;
35
+ default:
36
+ console.error(`Unknown command: ${command}`);
37
+ console.error('Run "axiomatic --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 };
@@ -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': '@axiomatic-labs/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': '@axiomatic-labs/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': '@axiomatic-labs/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,55 @@
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
+
8
+ async function run() {
9
+ // Check if already initialized
10
+ const versionFile = path.join(process.cwd(), '.claude', '.axiomatic-version');
11
+ if (fs.existsSync(versionFile)) {
12
+ const current = fs.readFileSync(versionFile, 'utf-8').trim();
13
+ console.error(`Already initialized at ${current}. Use "axiomatic update" instead.`);
14
+ process.exit(1);
15
+ }
16
+
17
+ console.log('Authenticating with GitHub...');
18
+ const token = requireAuth();
19
+
20
+ console.log('Fetching latest release...');
21
+ const release = await getLatestRelease(token);
22
+ const version = release.tag_name;
23
+
24
+ // Find the ZIP asset
25
+ const asset = release.assets.find((a) => a.name.endsWith('.zip'));
26
+ if (!asset) {
27
+ console.error('No ZIP asset found in the latest release.');
28
+ process.exit(1);
29
+ }
30
+
31
+ console.log(`Downloading ${asset.name} (${version})...`);
32
+ const zipBuffer = await downloadReleaseAsset(asset, token);
33
+
34
+ // Write ZIP to temp file and extract
35
+ const tmpZip = path.join(require('os').tmpdir(), `axiomatic-${Date.now()}.zip`);
36
+ fs.writeFileSync(tmpZip, zipBuffer);
37
+
38
+ try {
39
+ console.log('Extracting template files...');
40
+ execSync(`unzip -o "${tmpZip}" -d "${process.cwd()}"`, { stdio: 'pipe' });
41
+ } finally {
42
+ fs.unlinkSync(tmpZip);
43
+ }
44
+
45
+ // Ensure .claude/tmp exists
46
+ fs.mkdirSync(path.join(process.cwd(), '.claude', 'tmp'), { recursive: true });
47
+
48
+ // Write version file
49
+ writeLocalVersion(version);
50
+
51
+ console.log(`\nAxiomatic ${version} installed.`);
52
+ console.log('Run: claude, then /build');
53
+ }
54
+
55
+ module.exports = run;
@@ -0,0 +1,69 @@
1
+ // Canonical list of template files — mirrors build-zip.sh.
2
+ // Only these files are managed by init/update. Everything else is user-generated.
3
+
4
+ const TEMPLATE_SKILLS = [
5
+ 'build',
6
+ 'review',
7
+ 'setup',
8
+ 'brand-strategy',
9
+ 'competitive-analysis',
10
+ 'content-strategy',
11
+ 'design-specification',
12
+ 'information-architecture',
13
+ 'logo-design',
14
+ 'ux-wireframes',
15
+ 'visual-design-direction',
16
+ ];
17
+
18
+ const TEMPLATE_AGENTS = [
19
+ 'brand-strategy.md',
20
+ 'competitive-analysis.md',
21
+ 'content-strategy.md',
22
+ 'cro-layout.md',
23
+ 'design-mockup.md',
24
+ 'design-specification.md',
25
+ 'generate-skill.md',
26
+ 'information-architecture.md',
27
+ 'logo-design.md',
28
+ 'ux-wireframes.md',
29
+ 'visual-design-direction.md',
30
+ ];
31
+
32
+ // Build the full list of relative paths that belong to the template
33
+ function getTemplatePaths() {
34
+ const paths = [
35
+ 'CLAUDE.md',
36
+ '.claude/settings.json',
37
+ '.claude/docs/quality-gate-protocol.md',
38
+ ];
39
+
40
+ for (const skill of TEMPLATE_SKILLS) {
41
+ paths.push(`.claude/skills/${skill}/SKILL.md`);
42
+ }
43
+
44
+ for (const agent of TEMPLATE_AGENTS) {
45
+ paths.push(`.claude/agents/${agent}`);
46
+ }
47
+
48
+ // Hooks — all .js files are template-managed
49
+ // (matched dynamically during extraction since we don't know filenames ahead of time)
50
+
51
+ return paths;
52
+ }
53
+
54
+ // Check if a path from the ZIP is a template file
55
+ function isTemplatePath(relativePath) {
56
+ // Exact match for known files
57
+ const known = getTemplatePaths();
58
+ if (known.includes(relativePath)) return true;
59
+
60
+ // All hooks/*.js files are template-managed
61
+ if (relativePath.startsWith('.claude/hooks/') && relativePath.endsWith('.js')) return true;
62
+
63
+ // .axiomatic-version is also managed
64
+ if (relativePath === '.claude/.axiomatic-version') return true;
65
+
66
+ return false;
67
+ }
68
+
69
+ module.exports = { TEMPLATE_SKILLS, TEMPLATE_AGENTS, getTemplatePaths, isTemplatePath };
package/lib/update.js ADDED
@@ -0,0 +1,87 @@
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
+
9
+ async function run() {
10
+ const current = readLocalVersion();
11
+ if (!current) {
12
+ console.error('Not initialized. Run "axiomatic init" first.');
13
+ process.exit(1);
14
+ }
15
+
16
+ console.log('Authenticating with GitHub...');
17
+ const token = requireAuth();
18
+
19
+ console.log('Fetching latest release...');
20
+ const release = await getLatestRelease(token);
21
+ const latest = release.tag_name;
22
+
23
+ if (current === latest) {
24
+ console.log(`Already up to date (${current}).`);
25
+ return;
26
+ }
27
+
28
+ console.log(`Updating ${current} -> ${latest}`);
29
+
30
+ // Find the ZIP asset
31
+ const asset = release.assets.find((a) => a.name.endsWith('.zip'));
32
+ if (!asset) {
33
+ console.error('No ZIP asset found in the latest release.');
34
+ process.exit(1);
35
+ }
36
+
37
+ console.log(`Downloading ${asset.name}...`);
38
+ const zipBuffer = await downloadReleaseAsset(asset, token);
39
+
40
+ // Write ZIP to temp file
41
+ const tmpZip = path.join(require('os').tmpdir(), `axiomatic-${Date.now()}.zip`);
42
+ const tmpDir = path.join(require('os').tmpdir(), `axiomatic-extract-${Date.now()}`);
43
+ fs.writeFileSync(tmpZip, zipBuffer);
44
+
45
+ try {
46
+ // Extract to temp directory first
47
+ fs.mkdirSync(tmpDir, { recursive: true });
48
+ execSync(`unzip -o "${tmpZip}" -d "${tmpDir}"`, { stdio: 'pipe' });
49
+
50
+ // Copy only template files
51
+ const updated = [];
52
+ copyTemplateFiles(tmpDir, process.cwd(), '', updated);
53
+
54
+ // Write version file
55
+ writeLocalVersion(latest);
56
+
57
+ console.log(`\nUpdated ${current} -> ${latest}`);
58
+ if (updated.length > 0) {
59
+ console.log(`\nUpdated files:`);
60
+ for (const f of updated) {
61
+ console.log(` ${f}`);
62
+ }
63
+ }
64
+ console.log('\nRestart CLI to apply agent changes.');
65
+ } finally {
66
+ fs.unlinkSync(tmpZip);
67
+ fs.rmSync(tmpDir, { recursive: true, force: true });
68
+ }
69
+ }
70
+
71
+ function copyTemplateFiles(srcDir, destDir, prefix, updated) {
72
+ const entries = fs.readdirSync(path.join(srcDir, prefix), { withFileTypes: true });
73
+ for (const entry of entries) {
74
+ const relative = prefix ? `${prefix}/${entry.name}` : entry.name;
75
+ if (entry.isDirectory()) {
76
+ copyTemplateFiles(srcDir, destDir, relative, updated);
77
+ } else if (isTemplatePath(relative)) {
78
+ const src = path.join(srcDir, relative);
79
+ const dest = path.join(destDir, relative);
80
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
81
+ fs.copyFileSync(src, dest);
82
+ updated.push(relative);
83
+ }
84
+ }
85
+ }
86
+
87
+ module.exports = run;
package/lib/version.js ADDED
@@ -0,0 +1,57 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { getGitHubToken } = require('./auth.js');
4
+ const { getLatestRelease } = require('./download.js');
5
+
6
+ const VERSION_FILE = path.join(process.cwd(), '.claude', '.axiomatic-version');
7
+
8
+ function readLocalVersion() {
9
+ try {
10
+ return fs.readFileSync(VERSION_FILE, 'utf-8').trim();
11
+ } catch {
12
+ return null;
13
+ }
14
+ }
15
+
16
+ function writeLocalVersion(version) {
17
+ const dir = path.dirname(VERSION_FILE);
18
+ fs.mkdirSync(dir, { recursive: true });
19
+ fs.writeFileSync(VERSION_FILE, version + '\n');
20
+ }
21
+
22
+ async function run() {
23
+ const cliVersion = require('../package.json').version;
24
+ const local = readLocalVersion();
25
+
26
+ console.log(`CLI: v${cliVersion}`);
27
+
28
+ if (local) {
29
+ console.log(`Local: ${local}`);
30
+ } else {
31
+ console.log('Local: not installed (run "axiomatic init" first)');
32
+ }
33
+
34
+ const token = getGitHubToken();
35
+ if (!token) {
36
+ console.log('Latest: (authenticate with GitHub to check)');
37
+ return;
38
+ }
39
+
40
+ try {
41
+ const release = await getLatestRelease(token);
42
+ const latest = release.tag_name;
43
+ console.log(`Latest: ${latest}`);
44
+
45
+ if (local && local === latest) {
46
+ console.log('\nUp to date.');
47
+ } else if (local) {
48
+ console.log('\nUpdate available. Run: axiomatic update');
49
+ }
50
+ } catch (err) {
51
+ console.error(`\nCould not fetch latest release: ${err.message}`);
52
+ }
53
+ }
54
+
55
+ module.exports = run;
56
+ module.exports.readLocalVersion = readLocalVersion;
57
+ module.exports.writeLocalVersion = writeLocalVersion;
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "@axiomatic-labs/cli",
3
+ "version": "1.0.0",
4
+ "description": "CLI for installing and updating Axiomatic project templates",
5
+ "bin": {
6
+ "axiomatic": "./bin/cli.js"
7
+ },
8
+ "files": [
9
+ "bin/",
10
+ "lib/"
11
+ ],
12
+ "engines": {
13
+ "node": ">=18"
14
+ },
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/axiomatic-labs/axiomatic-cli.git"
18
+ },
19
+ "license": "UNLICENSED",
20
+ "private": false
21
+ }