agnix 0.7.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/README.md +88 -0
- package/bin/agnix +28 -0
- package/index.js +123 -0
- package/install.js +179 -0
- package/package.json +55 -0
package/README.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# agnix
|
|
2
|
+
|
|
3
|
+
Linter for AI agent configurations. Validates SKILL.md, CLAUDE.md, hooks, MCP, and more.
|
|
4
|
+
|
|
5
|
+
**100 rules** | **Real-time validation** | **Auto-fix** | **Multi-tool support**
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g agnix
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or run directly with npx:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx agnix .
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
### Command Line
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# Lint current directory
|
|
25
|
+
agnix .
|
|
26
|
+
|
|
27
|
+
# Lint specific file
|
|
28
|
+
agnix CLAUDE.md
|
|
29
|
+
|
|
30
|
+
# Auto-fix issues
|
|
31
|
+
agnix --fix .
|
|
32
|
+
|
|
33
|
+
# JSON output
|
|
34
|
+
agnix --format json .
|
|
35
|
+
|
|
36
|
+
# Target specific tool
|
|
37
|
+
agnix --target cursor .
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Node.js API
|
|
41
|
+
|
|
42
|
+
```javascript
|
|
43
|
+
const agnix = require('agnix');
|
|
44
|
+
|
|
45
|
+
// Async lint
|
|
46
|
+
const result = await agnix.lint('./');
|
|
47
|
+
console.log(result);
|
|
48
|
+
|
|
49
|
+
// Sync run
|
|
50
|
+
const { stdout, exitCode } = agnix.runSync(['--version']);
|
|
51
|
+
|
|
52
|
+
// Get version
|
|
53
|
+
console.log(agnix.version());
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Supported Files
|
|
57
|
+
|
|
58
|
+
| File | Tool |
|
|
59
|
+
|------|------|
|
|
60
|
+
| `SKILL.md` | Claude Code |
|
|
61
|
+
| `CLAUDE.md`, `AGENTS.md` | Claude Code, Codex |
|
|
62
|
+
| `.claude/settings.json` | Claude Code |
|
|
63
|
+
| `plugin.json` | Claude Code |
|
|
64
|
+
| `*.mcp.json` | All |
|
|
65
|
+
| `.github/copilot-instructions.md` | GitHub Copilot |
|
|
66
|
+
| `.cursor/rules/*.mdc` | Cursor |
|
|
67
|
+
|
|
68
|
+
## Options
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
-t, --target <tool> Target tool (ClaudeCode, Cursor, Copilot, CodexCli)
|
|
72
|
+
-f, --format <format> Output format (text, json, sarif)
|
|
73
|
+
--fix Auto-fix issues
|
|
74
|
+
-q, --quiet Only show errors
|
|
75
|
+
-v, --verbose Show detailed output
|
|
76
|
+
--version Show version
|
|
77
|
+
-h, --help Show help
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Links
|
|
81
|
+
|
|
82
|
+
- [GitHub Repository](https://github.com/avifenesh/agnix)
|
|
83
|
+
- [Validation Rules](https://github.com/avifenesh/agnix/blob/main/knowledge-base/VALIDATION-RULES.md)
|
|
84
|
+
- [VS Code Extension](https://marketplace.visualstudio.com/items?itemName=avifenesh.agnix)
|
|
85
|
+
|
|
86
|
+
## License
|
|
87
|
+
|
|
88
|
+
MIT OR Apache-2.0
|
package/bin/agnix
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { spawn } = require('child_process');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
|
|
7
|
+
const binDir = __dirname;
|
|
8
|
+
const binaryName = os.platform() === 'win32' ? 'agnix-binary.exe' : 'agnix-binary';
|
|
9
|
+
const binaryPath = path.join(binDir, binaryName);
|
|
10
|
+
|
|
11
|
+
const child = spawn(binaryPath, process.argv.slice(2), {
|
|
12
|
+
stdio: 'inherit',
|
|
13
|
+
shell: false,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
child.on('error', (err) => {
|
|
17
|
+
if (err.code === 'ENOENT') {
|
|
18
|
+
console.error('agnix binary not found. Try reinstalling:');
|
|
19
|
+
console.error(' npm install -g agnix');
|
|
20
|
+
} else {
|
|
21
|
+
console.error(`Failed to run agnix: ${err.message}`);
|
|
22
|
+
}
|
|
23
|
+
process.exit(1);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
child.on('exit', (code) => {
|
|
27
|
+
process.exit(code || 0);
|
|
28
|
+
});
|
package/index.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agnix - Node.js API
|
|
3
|
+
*
|
|
4
|
+
* Programmatic access to the agnix linter.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { spawn, spawnSync } = require('child_process');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const os = require('os');
|
|
10
|
+
|
|
11
|
+
const binDir = path.join(__dirname, 'bin');
|
|
12
|
+
const binaryName = os.platform() === 'win32' ? 'agnix-binary.exe' : 'agnix-binary';
|
|
13
|
+
const binaryPath = path.join(binDir, binaryName);
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Run agnix synchronously and return the result.
|
|
17
|
+
*
|
|
18
|
+
* @param {string[]} args - Command line arguments
|
|
19
|
+
* @param {object} options - Options passed to spawnSync
|
|
20
|
+
* @returns {{ stdout: string, stderr: string, exitCode: number }}
|
|
21
|
+
*/
|
|
22
|
+
function runSync(args = [], options = {}) {
|
|
23
|
+
const result = spawnSync(binaryPath, args, {
|
|
24
|
+
encoding: 'utf-8',
|
|
25
|
+
...options,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
if (result.error) {
|
|
29
|
+
throw result.error;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
stdout: result.stdout || '',
|
|
34
|
+
stderr: result.stderr || '',
|
|
35
|
+
exitCode: result.status || 0,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Run agnix asynchronously and return a promise.
|
|
41
|
+
*
|
|
42
|
+
* @param {string[]} args - Command line arguments
|
|
43
|
+
* @param {object} options - Options passed to spawn
|
|
44
|
+
* @returns {Promise<{ stdout: string, stderr: string, exitCode: number }>}
|
|
45
|
+
*/
|
|
46
|
+
function run(args = [], options = {}) {
|
|
47
|
+
return new Promise((resolve, reject) => {
|
|
48
|
+
const child = spawn(binaryPath, args, options);
|
|
49
|
+
let stdout = '';
|
|
50
|
+
let stderr = '';
|
|
51
|
+
|
|
52
|
+
if (child.stdout) {
|
|
53
|
+
child.stdout.on('data', (data) => {
|
|
54
|
+
stdout += data.toString();
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (child.stderr) {
|
|
59
|
+
child.stderr.on('data', (data) => {
|
|
60
|
+
stderr += data.toString();
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
child.on('error', reject);
|
|
65
|
+
|
|
66
|
+
child.on('close', (code) => {
|
|
67
|
+
resolve({
|
|
68
|
+
stdout,
|
|
69
|
+
stderr,
|
|
70
|
+
exitCode: code || 0,
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Lint files and return diagnostics as JSON.
|
|
78
|
+
*
|
|
79
|
+
* @param {string} target - Path to file or directory to lint
|
|
80
|
+
* @param {object} options - Lint options
|
|
81
|
+
* @param {string} options.target - Target tool (ClaudeCode, Cursor, etc.)
|
|
82
|
+
* @param {string} options.format - Output format (json, sarif)
|
|
83
|
+
* @returns {Promise<object>} - Parsed JSON diagnostics
|
|
84
|
+
*/
|
|
85
|
+
async function lint(target, options = {}) {
|
|
86
|
+
const args = ['--format', 'json'];
|
|
87
|
+
|
|
88
|
+
if (options.target) {
|
|
89
|
+
args.push('--target', options.target);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
args.push(target);
|
|
93
|
+
|
|
94
|
+
const result = await run(args);
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
return JSON.parse(result.stdout);
|
|
98
|
+
} catch {
|
|
99
|
+
return {
|
|
100
|
+
files: [],
|
|
101
|
+
summary: { errors: 0, warnings: 0, fixable: 0 },
|
|
102
|
+
raw: result.stdout,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Get agnix version.
|
|
109
|
+
*
|
|
110
|
+
* @returns {string} - Version string
|
|
111
|
+
*/
|
|
112
|
+
function version() {
|
|
113
|
+
const result = runSync(['--version']);
|
|
114
|
+
return result.stdout.trim();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
module.exports = {
|
|
118
|
+
run,
|
|
119
|
+
runSync,
|
|
120
|
+
lint,
|
|
121
|
+
version,
|
|
122
|
+
binaryPath,
|
|
123
|
+
};
|
package/install.js
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const https = require('https');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const { execSync } = require('child_process');
|
|
7
|
+
const os = require('os');
|
|
8
|
+
|
|
9
|
+
const GITHUB_REPO = 'avifenesh/agnix';
|
|
10
|
+
const VERSION = require('./package.json').version;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Get platform-specific asset name and binary name.
|
|
14
|
+
*/
|
|
15
|
+
function getPlatformInfo() {
|
|
16
|
+
const platform = os.platform();
|
|
17
|
+
const arch = os.arch();
|
|
18
|
+
|
|
19
|
+
const mapping = {
|
|
20
|
+
'darwin-arm64': {
|
|
21
|
+
asset: 'agnix-aarch64-apple-darwin.tar.gz',
|
|
22
|
+
extractedName: 'agnix',
|
|
23
|
+
binary: 'agnix-binary',
|
|
24
|
+
},
|
|
25
|
+
'darwin-x64': {
|
|
26
|
+
// x64 Mac uses ARM binary via Rosetta 2
|
|
27
|
+
asset: 'agnix-aarch64-apple-darwin.tar.gz',
|
|
28
|
+
extractedName: 'agnix',
|
|
29
|
+
binary: 'agnix-binary',
|
|
30
|
+
},
|
|
31
|
+
'linux-x64': {
|
|
32
|
+
asset: 'agnix-x86_64-unknown-linux-gnu.tar.gz',
|
|
33
|
+
extractedName: 'agnix',
|
|
34
|
+
binary: 'agnix-binary',
|
|
35
|
+
},
|
|
36
|
+
'linux-arm64': {
|
|
37
|
+
// ARM Linux not yet available, try x64 with emulation
|
|
38
|
+
asset: 'agnix-x86_64-unknown-linux-gnu.tar.gz',
|
|
39
|
+
extractedName: 'agnix',
|
|
40
|
+
binary: 'agnix-binary',
|
|
41
|
+
},
|
|
42
|
+
'win32-x64': {
|
|
43
|
+
asset: 'agnix-x86_64-pc-windows-msvc.zip',
|
|
44
|
+
extractedName: 'agnix.exe',
|
|
45
|
+
binary: 'agnix-binary.exe',
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const key = `${platform}-${arch}`;
|
|
50
|
+
const info = mapping[key];
|
|
51
|
+
|
|
52
|
+
if (!info) {
|
|
53
|
+
console.error(`Unsupported platform: ${platform}-${arch}`);
|
|
54
|
+
console.error('Supported platforms: darwin-arm64, darwin-x64, linux-x64, win32-x64');
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return info;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Download a file from URL, following redirects.
|
|
63
|
+
*/
|
|
64
|
+
function downloadFile(url, destPath) {
|
|
65
|
+
return new Promise((resolve, reject) => {
|
|
66
|
+
const file = fs.createWriteStream(destPath);
|
|
67
|
+
|
|
68
|
+
const request = https.get(url, (response) => {
|
|
69
|
+
if (response.statusCode === 302 || response.statusCode === 301) {
|
|
70
|
+
const redirectUrl = response.headers.location;
|
|
71
|
+
if (redirectUrl) {
|
|
72
|
+
file.close();
|
|
73
|
+
fs.unlinkSync(destPath);
|
|
74
|
+
downloadFile(redirectUrl, destPath).then(resolve).catch(reject);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (response.statusCode !== 200) {
|
|
80
|
+
file.close();
|
|
81
|
+
reject(new Error(`Download failed with status ${response.statusCode}`));
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
response.pipe(file);
|
|
86
|
+
|
|
87
|
+
file.on('finish', () => {
|
|
88
|
+
file.close();
|
|
89
|
+
resolve();
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
request.on('error', (err) => {
|
|
94
|
+
file.close();
|
|
95
|
+
fs.unlinkSync(destPath);
|
|
96
|
+
reject(err);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
file.on('error', (err) => {
|
|
100
|
+
fs.unlinkSync(destPath);
|
|
101
|
+
reject(err);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Extract archive based on platform.
|
|
108
|
+
*/
|
|
109
|
+
function extractArchive(archivePath, destDir) {
|
|
110
|
+
const platform = os.platform();
|
|
111
|
+
|
|
112
|
+
if (platform === 'win32') {
|
|
113
|
+
// Use PowerShell to extract zip
|
|
114
|
+
execSync(
|
|
115
|
+
`powershell -Command "Expand-Archive -Path '${archivePath}' -DestinationPath '${destDir}' -Force"`,
|
|
116
|
+
{ stdio: 'inherit' }
|
|
117
|
+
);
|
|
118
|
+
} else {
|
|
119
|
+
// Use tar for .tar.gz
|
|
120
|
+
execSync(`tar -xzf "${archivePath}" -C "${destDir}"`, { stdio: 'inherit' });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function main() {
|
|
125
|
+
const platformInfo = getPlatformInfo();
|
|
126
|
+
const binDir = path.join(__dirname, 'bin');
|
|
127
|
+
const binaryPath = path.join(binDir, platformInfo.binary);
|
|
128
|
+
const extractedPath = path.join(binDir, platformInfo.extractedName);
|
|
129
|
+
|
|
130
|
+
// Skip if binary already exists
|
|
131
|
+
if (fs.existsSync(binaryPath)) {
|
|
132
|
+
console.log('agnix binary already installed');
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Ensure bin directory exists
|
|
137
|
+
if (!fs.existsSync(binDir)) {
|
|
138
|
+
fs.mkdirSync(binDir, { recursive: true });
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const downloadUrl = `https://github.com/${GITHUB_REPO}/releases/download/v${VERSION}/${platformInfo.asset}`;
|
|
142
|
+
const archivePath = path.join(binDir, platformInfo.asset);
|
|
143
|
+
|
|
144
|
+
console.log(`Downloading agnix v${VERSION} for ${os.platform()}-${os.arch()}...`);
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
await downloadFile(downloadUrl, archivePath);
|
|
148
|
+
console.log('Extracting...');
|
|
149
|
+
extractArchive(archivePath, binDir);
|
|
150
|
+
|
|
151
|
+
// Clean up archive
|
|
152
|
+
fs.unlinkSync(archivePath);
|
|
153
|
+
|
|
154
|
+
// Rename extracted binary to avoid conflict with wrapper script
|
|
155
|
+
if (fs.existsSync(extractedPath) && extractedPath !== binaryPath) {
|
|
156
|
+
fs.renameSync(extractedPath, binaryPath);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Make binary executable on Unix
|
|
160
|
+
if (os.platform() !== 'win32') {
|
|
161
|
+
fs.chmodSync(binaryPath, 0o755);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Verify binary exists
|
|
165
|
+
if (!fs.existsSync(binaryPath)) {
|
|
166
|
+
throw new Error('Binary not found after extraction');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
console.log('agnix installed successfully');
|
|
170
|
+
} catch (error) {
|
|
171
|
+
console.error(`Failed to install agnix: ${error.message}`);
|
|
172
|
+
console.error('');
|
|
173
|
+
console.error('You can install manually:');
|
|
174
|
+
console.error(' cargo install agnix-cli');
|
|
175
|
+
process.exit(1);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
main();
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "agnix",
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"description": "Linter for AI agent configurations. Validates SKILL.md, CLAUDE.md, hooks, MCP, and more.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"agent",
|
|
7
|
+
"linter",
|
|
8
|
+
"claude",
|
|
9
|
+
"claude-code",
|
|
10
|
+
"mcp",
|
|
11
|
+
"model-context-protocol",
|
|
12
|
+
"skills",
|
|
13
|
+
"validation",
|
|
14
|
+
"cursor",
|
|
15
|
+
"copilot",
|
|
16
|
+
"codex",
|
|
17
|
+
"ai"
|
|
18
|
+
],
|
|
19
|
+
"homepage": "https://github.com/avifenesh/agnix",
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/avifenesh/agnix/issues"
|
|
22
|
+
},
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://github.com/avifenesh/agnix.git"
|
|
26
|
+
},
|
|
27
|
+
"license": "MIT OR Apache-2.0",
|
|
28
|
+
"author": "Avi Fenesh <avifenesh@gmail.com>",
|
|
29
|
+
"bin": {
|
|
30
|
+
"agnix": "bin/agnix"
|
|
31
|
+
},
|
|
32
|
+
"main": "index.js",
|
|
33
|
+
"files": [
|
|
34
|
+
"bin/",
|
|
35
|
+
"install.js",
|
|
36
|
+
"index.js",
|
|
37
|
+
"README.md"
|
|
38
|
+
],
|
|
39
|
+
"scripts": {
|
|
40
|
+
"postinstall": "node install.js",
|
|
41
|
+
"test": "node test/test.js"
|
|
42
|
+
},
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=16.0.0"
|
|
45
|
+
},
|
|
46
|
+
"os": [
|
|
47
|
+
"darwin",
|
|
48
|
+
"linux",
|
|
49
|
+
"win32"
|
|
50
|
+
],
|
|
51
|
+
"cpu": [
|
|
52
|
+
"x64",
|
|
53
|
+
"arm64"
|
|
54
|
+
]
|
|
55
|
+
}
|