agnostic-ai 0.62.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 +22 -0
- package/bin/agnostic-ai.js +27 -0
- package/lib/download.js +122 -0
- package/lib/download_test.js +62 -0
- package/package.json +52 -0
- package/scripts/postinstall.js +12 -0
package/README.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# agnostic-ai
|
|
2
|
+
|
|
3
|
+
One spec, every AI CLI. Write your agents, skills, rules, hooks, and MCP servers once, then emit them to Claude Code, Codex, Gemini, Cursor, Copilot, and 20 more in each tool's native format.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npx agnostic-ai init --demo # scaffold specs, one example per kind
|
|
7
|
+
npx agnostic-ai sync # emit native config for every target
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Or install it globally:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install -g agnostic-ai
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
This package is a thin wrapper: it downloads the prebuilt Go binary for your platform from [GitHub Releases](https://github.com/Chemaclass/agnostic-ai/releases) and runs it. Supported platforms are macOS, Linux, and Windows on x64 and arm64.
|
|
17
|
+
|
|
18
|
+
The download normally happens on install. Under npm's install-script gating (`--ignore-scripts`, or npm 11's default prompt), it happens on first run instead. Pin a different binary version with `AGNOSTIC_AI_VERSION=v0.45.0`.
|
|
19
|
+
|
|
20
|
+
Full docs, targets, and configuration: [github.com/Chemaclass/agnostic-ai](https://github.com/Chemaclass/agnostic-ai).
|
|
21
|
+
|
|
22
|
+
MIT licensed.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
const { spawnSync } = require('node:child_process')
|
|
5
|
+
const { ensureBinary } = require('../lib/download')
|
|
6
|
+
|
|
7
|
+
async function main() {
|
|
8
|
+
let binary
|
|
9
|
+
try {
|
|
10
|
+
// Normally already on disk from postinstall; this covers an install that
|
|
11
|
+
// ran with --ignore-scripts or without network.
|
|
12
|
+
binary = await ensureBinary({ log: (m) => console.error(`agnostic-ai: ${m}`) })
|
|
13
|
+
} catch (err) {
|
|
14
|
+
console.error(`agnostic-ai: ${err.message}`)
|
|
15
|
+
process.exit(1)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const result = spawnSync(binary, process.argv.slice(2), { stdio: 'inherit' })
|
|
19
|
+
if (result.error) {
|
|
20
|
+
console.error(`agnostic-ai: ${result.error.message}`)
|
|
21
|
+
process.exit(1)
|
|
22
|
+
}
|
|
23
|
+
// A signalled child reports null status; 128+signal is the shell convention.
|
|
24
|
+
process.exit(result.status === null ? 128 : result.status)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
main()
|
package/lib/download.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// Fetches the agnostic-ai release binary for the current platform. Shared by
|
|
4
|
+
// the postinstall hook and the bin shim, so a failed install still recovers on
|
|
5
|
+
// first run instead of leaving a broken command.
|
|
6
|
+
|
|
7
|
+
const { execFileSync } = require('node:child_process')
|
|
8
|
+
const crypto = require('node:crypto')
|
|
9
|
+
const fs = require('node:fs')
|
|
10
|
+
const https = require('node:https')
|
|
11
|
+
const os = require('node:os')
|
|
12
|
+
const path = require('node:path')
|
|
13
|
+
|
|
14
|
+
const REPO = 'Chemaclass/agnostic-ai'
|
|
15
|
+
const BINARY = process.platform === 'win32' ? 'agnostic-ai.exe' : 'agnostic-ai'
|
|
16
|
+
|
|
17
|
+
const PLATFORMS = { darwin: 'darwin', linux: 'linux', win32: 'windows' }
|
|
18
|
+
const ARCHS = { x64: 'amd64', arm64: 'arm64' }
|
|
19
|
+
|
|
20
|
+
function target() {
|
|
21
|
+
const goos = PLATFORMS[process.platform]
|
|
22
|
+
const goarch = ARCHS[process.arch]
|
|
23
|
+
if (!goos || !goarch) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
`agnostic-ai has no prebuilt binary for ${process.platform}/${process.arch}. ` +
|
|
26
|
+
'Build from source: go install github.com/chemaclass/agnostic-ai/cmd/agnostic-ai@latest'
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
return { goos, goarch, ext: goos === 'windows' ? 'zip' : 'tar.gz' }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function assetName() {
|
|
33
|
+
const { goos, goarch, ext } = target()
|
|
34
|
+
return `agnostic-ai_${goos}_${goarch}.${ext}`
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function binaryPath() {
|
|
38
|
+
return path.join(__dirname, '..', 'bin', BINARY)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function get(url) {
|
|
42
|
+
return new Promise((resolve, reject) => {
|
|
43
|
+
https
|
|
44
|
+
.get(url, { headers: { 'user-agent': 'agnostic-ai-npm' } }, (res) => {
|
|
45
|
+
// GitHub redirects release assets to a signed object-store URL.
|
|
46
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
47
|
+
res.resume()
|
|
48
|
+
resolve(get(res.headers.location))
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
if (res.statusCode !== 200) {
|
|
52
|
+
res.resume()
|
|
53
|
+
reject(new Error(`GET ${url} failed with HTTP ${res.statusCode}`))
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
const chunks = []
|
|
57
|
+
res.on('data', (c) => chunks.push(c))
|
|
58
|
+
res.on('end', () => resolve(Buffer.concat(chunks)))
|
|
59
|
+
res.on('error', reject)
|
|
60
|
+
})
|
|
61
|
+
.on('error', reject)
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// The published package carries the release version; a checkout carries the
|
|
66
|
+
// 0.0.0-dev placeholder, which has no matching release, so fall back to latest.
|
|
67
|
+
async function resolveVersion() {
|
|
68
|
+
if (process.env.AGNOSTIC_AI_VERSION) return process.env.AGNOSTIC_AI_VERSION
|
|
69
|
+
|
|
70
|
+
const { version } = require('../package.json')
|
|
71
|
+
if (version && !version.startsWith('0.0.0')) return `v${version}`
|
|
72
|
+
|
|
73
|
+
const body = await get(`https://api.github.com/repos/${REPO}/releases/latest`)
|
|
74
|
+
const tag = JSON.parse(body.toString('utf8')).tag_name
|
|
75
|
+
if (!tag) throw new Error('could not resolve the latest agnostic-ai release')
|
|
76
|
+
return tag
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function downloadUrl(version, asset) {
|
|
80
|
+
return `https://github.com/${REPO}/releases/download/${version}/${asset}`
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function verifyChecksum(archive, asset, version) {
|
|
84
|
+
const sums = (await get(downloadUrl(version, 'checksums.txt'))).toString('utf8')
|
|
85
|
+
const line = sums.split('\n').find((l) => l.trim().endsWith(asset))
|
|
86
|
+
if (!line) throw new Error(`${asset} missing from checksums.txt`)
|
|
87
|
+
|
|
88
|
+
const expected = line.trim().split(/\s+/)[0]
|
|
89
|
+
const actual = crypto.createHash('sha256').update(fs.readFileSync(archive)).digest('hex')
|
|
90
|
+
if (actual !== expected) throw new Error(`checksum mismatch for ${asset}`)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function ensureBinary({ log = () => {} } = {}) {
|
|
94
|
+
const dest = binaryPath()
|
|
95
|
+
if (fs.existsSync(dest)) return dest
|
|
96
|
+
|
|
97
|
+
const version = await resolveVersion()
|
|
98
|
+
const asset = assetName()
|
|
99
|
+
log(`downloading agnostic-ai ${version} (${asset})`)
|
|
100
|
+
|
|
101
|
+
const work = fs.mkdtempSync(path.join(os.tmpdir(), 'agnostic-ai-'))
|
|
102
|
+
try {
|
|
103
|
+
const archive = path.join(work, asset)
|
|
104
|
+
fs.writeFileSync(archive, await get(downloadUrl(version, asset)))
|
|
105
|
+
await verifyChecksum(archive, asset, version)
|
|
106
|
+
|
|
107
|
+
// bsdtar reads both tar.gz and zip, and ships with macOS and Windows 10
|
|
108
|
+
// 1803+; Linux only ever gets the tar.gz here, so GNU tar is fine too.
|
|
109
|
+
execFileSync('tar', ['-xf', archive, '-C', work, BINARY], { stdio: 'ignore' })
|
|
110
|
+
|
|
111
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true })
|
|
112
|
+
fs.copyFileSync(path.join(work, BINARY), dest)
|
|
113
|
+
fs.chmodSync(dest, 0o755)
|
|
114
|
+
} finally {
|
|
115
|
+
fs.rmSync(work, { recursive: true, force: true })
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
log(`installed ${dest}`)
|
|
119
|
+
return dest
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
module.exports = { assetName, binaryPath, downloadUrl, ensureBinary, resolveVersion, target }
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// Run: node npm/lib/download_test.js (or npm test inside npm/)
|
|
4
|
+
//
|
|
5
|
+
// Covers the pure mapping logic. The download path itself is exercised
|
|
6
|
+
// end to end by .github/workflows/install.yml against a real release.
|
|
7
|
+
|
|
8
|
+
const assert = require('node:assert')
|
|
9
|
+
const path = require('node:path')
|
|
10
|
+
const { assetName, binaryPath, downloadUrl, resolveVersion, target } = require('./download')
|
|
11
|
+
|
|
12
|
+
const tests = {
|
|
13
|
+
'asset name matches the release archive for this platform'() {
|
|
14
|
+
const { goos, goarch } = target()
|
|
15
|
+
const expected = goos === 'windows'
|
|
16
|
+
? `agnostic-ai_${goos}_${goarch}.zip`
|
|
17
|
+
: `agnostic-ai_${goos}_${goarch}.tar.gz`
|
|
18
|
+
assert.strictEqual(assetName(), expected)
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
'windows gets a zip, every other platform a tar.gz'() {
|
|
22
|
+
assert.strictEqual(target().ext, process.platform === 'win32' ? 'zip' : 'tar.gz')
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
'download url points at the tagged release asset'() {
|
|
26
|
+
assert.strictEqual(
|
|
27
|
+
downloadUrl('v0.45.0', 'agnostic-ai_linux_amd64.tar.gz'),
|
|
28
|
+
'https://github.com/Chemaclass/agnostic-ai/releases/download/v0.45.0/agnostic-ai_linux_amd64.tar.gz'
|
|
29
|
+
)
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
'binary path lands in the package bin dir'() {
|
|
33
|
+
assert.strictEqual(path.dirname(binaryPath()), path.join(__dirname, '..', 'bin'))
|
|
34
|
+
assert.match(path.basename(binaryPath()), /^agnostic-ai(\.exe)?$/)
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
async 'env override wins over the package version'() {
|
|
38
|
+
process.env.AGNOSTIC_AI_VERSION = 'v1.2.3'
|
|
39
|
+
try {
|
|
40
|
+
assert.strictEqual(await resolveVersion(), 'v1.2.3')
|
|
41
|
+
} finally {
|
|
42
|
+
delete process.env.AGNOSTIC_AI_VERSION
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function run() {
|
|
48
|
+
let failed = 0
|
|
49
|
+
for (const [name, fn] of Object.entries(tests)) {
|
|
50
|
+
try {
|
|
51
|
+
await fn()
|
|
52
|
+
console.log(`ok ${name}`)
|
|
53
|
+
} catch (err) {
|
|
54
|
+
failed++
|
|
55
|
+
console.error(`FAIL ${name}\n ${err.message}`)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
console.log(`\n${Object.keys(tests).length - failed} passed, ${failed} failed`)
|
|
59
|
+
process.exit(failed === 0 ? 0 : 1)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
run()
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "agnostic-ai",
|
|
3
|
+
"version": "0.62.0",
|
|
4
|
+
"description": "One spec, every AI CLI. Sync agents, skills, rules, hooks, and MCP servers to Claude Code, Codex, Gemini, Cursor, Copilot, and 20 more.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"ai",
|
|
7
|
+
"cli",
|
|
8
|
+
"claude",
|
|
9
|
+
"codex",
|
|
10
|
+
"cursor",
|
|
11
|
+
"copilot",
|
|
12
|
+
"agents",
|
|
13
|
+
"skills",
|
|
14
|
+
"mcp"
|
|
15
|
+
],
|
|
16
|
+
"homepage": "https://github.com/Chemaclass/agnostic-ai#readme",
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/Chemaclass/agnostic-ai/issues"
|
|
19
|
+
},
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/Chemaclass/agnostic-ai.git",
|
|
23
|
+
"directory": "npm"
|
|
24
|
+
},
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"author": "Chemaclass",
|
|
27
|
+
"bin": {
|
|
28
|
+
"agnostic-ai": "bin/agnostic-ai.js"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"bin/",
|
|
32
|
+
"lib/",
|
|
33
|
+
"scripts/",
|
|
34
|
+
"README.md"
|
|
35
|
+
],
|
|
36
|
+
"scripts": {
|
|
37
|
+
"postinstall": "node scripts/postinstall.js",
|
|
38
|
+
"test": "node lib/download_test.js"
|
|
39
|
+
},
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=18"
|
|
42
|
+
},
|
|
43
|
+
"os": [
|
|
44
|
+
"darwin",
|
|
45
|
+
"linux",
|
|
46
|
+
"win32"
|
|
47
|
+
],
|
|
48
|
+
"cpu": [
|
|
49
|
+
"x64",
|
|
50
|
+
"arm64"
|
|
51
|
+
]
|
|
52
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// Downloads the binary at install time. A failure here is a warning, never an
|
|
4
|
+
// install error: the bin shim retries on first run, so an offline install or a
|
|
5
|
+
// blocked proxy does not break `npm install` for the whole project.
|
|
6
|
+
|
|
7
|
+
const { ensureBinary } = require('../lib/download')
|
|
8
|
+
|
|
9
|
+
ensureBinary({ log: (m) => console.log(`agnostic-ai: ${m}`) }).catch((err) => {
|
|
10
|
+
console.warn(`agnostic-ai: ${err.message}`)
|
|
11
|
+
console.warn('agnostic-ai: the binary will be fetched on first run instead')
|
|
12
|
+
})
|