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 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()
@@ -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
+ })