@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 +39 -0
- package/lib/auth.js +32 -0
- package/lib/download.js +99 -0
- package/lib/init.js +55 -0
- package/lib/manifest.js +69 -0
- package/lib/update.js +87 -0
- package/lib/version.js +57 -0
- package/package.json +21 -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(`@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 };
|
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': '@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;
|
package/lib/manifest.js
ADDED
|
@@ -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
|
+
}
|