arcana-ai 0.2.1 → 0.2.3

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.
Files changed (2) hide show
  1. package/bin/arcana.js +131 -134
  2. package/package.json +11 -2
package/bin/arcana.js CHANGED
@@ -1,134 +1,131 @@
1
- #!/usr/bin/env node
2
- // SPDX-License-Identifier: MIT OR LicenseRef-arcana-Commercial
3
- // Copyright (c) 2026 arcana contributors
4
- // arcana launcher — downloads the binary from GitHub releases if needed, then runs it.
5
- // Entrypoint for: npx arcana-ai
6
- const { spawnSync, execSync } = require("child_process")
7
- const { existsSync, mkdirSync, chmodSync, writeFileSync, unlinkSync } = require("fs")
8
- const path = require("path")
9
- const crypto = require("crypto")
10
- const os = require("os")
11
-
12
- const REPO = "Lento47/arcana"
13
- const VERSION = "v0.1.2"
14
-
15
- const PLATFORM_MAP = {
16
- "win32-x64": { asset: "arcana-windows-x64.zip", binary: "arcana.exe" },
17
- "win32-arm64": { asset: "arcana-windows-arm64.zip", binary: "arcana.exe" },
18
- "linux-x64": { asset: "arcana-linux-x64.tar.gz", binary: "arcana" },
19
- "linux-arm64": { asset: "arcana-linux-arm64.tar.gz", binary: "arcana" },
20
- "darwin-x64": { asset: "arcana-darwin-x64.zip", binary: "arcana" },
21
- "darwin-arm64": { asset: "arcana-darwin-arm64.zip", binary: "arcana" },
22
- }
23
-
24
- const platform = `${os.platform()}-${os.arch()}`
25
- const entry = PLATFORM_MAP[platform]
26
-
27
- if (!entry) {
28
- console.error(`arcana: unsupported platform ${platform}`)
29
- console.error(`arcana: try installing from source: https://github.com/Lento47/arcana`)
30
- process.exit(1)
31
- }
32
-
33
- const CACHE_DIR = process.env.ARCANA_CACHE || path.join(os.homedir(), ".arcana", "bin")
34
- const CACHED_BINARY = path.join(CACHE_DIR, entry.binary)
35
-
36
- async function downloadAndExtract() {
37
- const ext = entry.asset.endsWith(".tar.gz") ? ".tar.gz" : ".zip"
38
- const zipName = `arcana-${platform}${ext}`
39
-
40
- // Clean up any stale temp file from previous failed attempts
41
- try { unlinkSync(path.join(CACHE_DIR, zipName)) } catch {}
42
-
43
- const url = `https://github.com/${REPO}/releases/download/${VERSION}/${entry.asset}`
44
- console.error(`arcana: downloading ${zipName}...`)
45
-
46
- mkdirSync(CACHE_DIR, { recursive: true })
47
-
48
- const res = await fetch(url)
49
- if (!res.ok) {
50
- console.error(`arcana: download failed: ${res.status} ${res.statusText}`)
51
- process.exit(1)
52
- }
53
-
54
- const tmp = path.join(CACHE_DIR, zipName)
55
- const buf = Buffer.from(await res.arrayBuffer())
56
- writeFileSync(tmp, buf)
57
- console.error(`arcana: ${(buf.length / 1e6).toFixed(1)}MB, verifying checksum...`)
58
-
59
- // Verify binary checksum against GitHub release .sha256 artifact
60
- const shaUrl = url + ".sha256"
61
- try {
62
- const shaRes = await fetch(shaUrl)
63
- if (!shaRes.ok) {
64
- console.error(`arcana: checksum file not found (${shaUrl}), skipping verification`)
65
- } else {
66
- const shaText = (await shaRes.text()).trim()
67
- const expectedHash = shaText.split(/\s+/)[0]
68
- const actualHash = crypto.createHash("sha256").update(buf).digest("hex")
69
- if (expectedHash !== actualHash) {
70
- console.error(`arcana: CHECKSUM MISMATCH for ${zipName}`)
71
- console.error(` expected: ${expectedHash}`)
72
- console.error(` actual: ${actualHash}`)
73
- console.error(`arcana: binary may be corrupted or tampered — deleting`)
74
- try { unlinkSync(tmp) } catch {}
75
- process.exit(1)
76
- }
77
- console.error(`arcana: checksum OK (SHA256: ${actualHash.slice(0, 16)}...)`)
78
- }
79
- } catch (e) {
80
- console.error(`arcana: checksum verification failed: ${e.message}`)
81
- process.exit(1)
82
- }
83
-
84
- console.error(`arcana: extracting...`)
85
-
86
- try {
87
- if (entry.asset.endsWith(".tar.gz")) {
88
- execSync(`tar xzf "${tmp}" -C "${CACHE_DIR}"`, { stdio: "pipe" })
89
- unlinkSync(tmp)
90
- } else if (entry.asset.endsWith(".zip")) {
91
- if (os.platform() === "win32") {
92
- // .NET ZipFile — built into .NET, no PowerShell module needed
93
- const safeTmp = tmp.replace(/'/g, "''")
94
- const safeDir = CACHE_DIR.replace(/'/g, "''")
95
- execSync(
96
- `powershell -Command "[System.Reflection.Assembly]::LoadWithPartialName('System.IO.Compression.FileSystem') | Out-Null; [System.IO.Compression.ZipFile]::ExtractToDirectory('${safeTmp}', '${safeDir}')"`,
97
- { stdio: "pipe" },
98
- )
99
- } else {
100
- execSync(`unzip -o "${tmp}" -d "${CACHE_DIR}"`, { stdio: "pipe" })
101
- }
102
- unlinkSync(tmp)
103
- }
104
- } catch (e) {
105
- console.error(`arcana: extraction failed: ${e.message}`)
106
- process.exit(1)
107
- }
108
-
109
- if (os.platform() !== "win32") {
110
- try { chmodSync(CACHED_BINARY, 0o755) } catch {}
111
- }
112
-
113
- console.error(`arcana: ready ${CACHED_BINARY}`)
114
- }
115
-
116
- async function main() {
117
- if (!existsSync(CACHED_BINARY)) {
118
- await downloadAndExtract()
119
- }
120
-
121
- if (!existsSync(CACHED_BINARY)) {
122
- console.error(`arcana: binary not found: ${CACHED_BINARY}`)
123
- process.exit(1)
124
- }
125
-
126
- const args = process.argv.slice(2)
127
- const child = spawnSync(CACHED_BINARY, args, { stdio: "inherit" })
128
- process.exit(child.status ?? 0)
129
- }
130
-
131
- main().catch((err) => {
132
- console.error(`arcana: ${err.message}`)
133
- process.exit(1)
134
- })
1
+ #!/usr/bin/env node
2
+ // SPDX-License-Identifier: MIT OR LicenseRef-arcana-Commercial
3
+ // Copyright (c) 2026 arcana contributors
4
+ // arcana launcher — downloads the binary from GitHub releases if needed, then runs it.
5
+ // Entrypoint for: npx arcana-ai
6
+ const { spawnSync, execSync } = require("child_process")
7
+ const { existsSync, mkdirSync, chmodSync, writeFileSync, unlinkSync } = require("fs")
8
+ const path = require("path")
9
+ const crypto = require("crypto")
10
+ const os = require("os")
11
+
12
+ const REPO = "Lento47/arcana"
13
+ const VERSION = "v0.2.3"
14
+
15
+ const PLATFORM_MAP = {
16
+ "win32-x64": { asset: "arcana-windows-x64.zip", binary: "arcana.exe" },
17
+ "win32-arm64": { asset: "arcana-windows-arm64.zip", binary: "arcana.exe" },
18
+ "linux-x64": { asset: "arcana-linux-x64.tar.gz", binary: "arcana" },
19
+ "linux-arm64": { asset: "arcana-linux-arm64.tar.gz", binary: "arcana" },
20
+ "darwin-x64": { asset: "arcana-darwin-x64.zip", binary: "arcana" },
21
+ "darwin-arm64": { asset: "arcana-darwin-arm64.zip", binary: "arcana" },
22
+ }
23
+
24
+ const platform = `${os.platform()}-${os.arch()}`
25
+ const entry = PLATFORM_MAP[platform]
26
+
27
+ if (!entry) {
28
+ console.error(`arcana: unsupported platform ${platform}`)
29
+ console.error(`arcana: try installing from source: https://github.com/Lento47/arcana`)
30
+ process.exit(1)
31
+ }
32
+
33
+ const CACHE_DIR = process.env.ARCANA_CACHE || path.join(os.homedir(), ".arcana", "bin")
34
+ const CACHED_BINARY = path.join(CACHE_DIR, entry.binary)
35
+
36
+ async function downloadAndExtract() {
37
+ const ext = entry.asset.endsWith(".tar.gz") ? ".tar.gz" : ".zip"
38
+ const zipName = `arcana-${platform}${ext}`
39
+
40
+ // Clean up any stale temp file from previous failed attempts
41
+ try { unlinkSync(path.join(CACHE_DIR, zipName)) } catch {}
42
+
43
+ const url = `https://github.com/${REPO}/releases/download/${VERSION}/${entry.asset}`
44
+ console.error(`arcana: downloading ${zipName}...`)
45
+
46
+ mkdirSync(CACHE_DIR, { recursive: true })
47
+
48
+ const res = await fetch(url)
49
+ if (!res.ok) {
50
+ console.error(`arcana: download failed: ${res.status} ${res.statusText}`)
51
+ process.exit(1)
52
+ }
53
+
54
+ const tmp = path.join(CACHE_DIR, zipName)
55
+ const buf = Buffer.from(await res.arrayBuffer())
56
+ writeFileSync(tmp, buf)
57
+ console.error(`arcana: ${(buf.length / 1e6).toFixed(1)}MB, verifying checksum...`)
58
+
59
+ // Verify binary integrity — checksum is mandatory, GitHub generates .sha256 for every asset
60
+ const shaUrl = url + ".sha256"
61
+ const shaRes = await fetch(shaUrl)
62
+ if (!shaRes.ok) {
63
+ console.error(`arcana: FATAL — checksum unavailable (${shaUrl})`)
64
+ console.error(`arcana: the binary cannot be verified and will not be executed`)
65
+ try { unlinkSync(tmp) } catch {}
66
+ process.exit(1)
67
+ }
68
+ const shaText = (await shaRes.text()).trim()
69
+ const expectedHash = shaText.split(/\s+/)[0]
70
+ const actualHash = crypto.createHash("sha256").update(buf).digest("hex")
71
+ if (expectedHash !== actualHash) {
72
+ console.error(`arcana: CHECKSUM MISMATCH for ${zipName}`)
73
+ console.error(` expected: ${expectedHash}`)
74
+ console.error(` actual: ${actualHash}`)
75
+ console.error(`arcana: binary may be corrupted or tampered — deleting`)
76
+ try { unlinkSync(tmp) } catch {}
77
+ process.exit(1)
78
+ }
79
+ console.error(`arcana: checksum OK (SHA256: ${actualHash.slice(0, 16)}...)`)
80
+
81
+ console.error(`arcana: extracting...`)
82
+
83
+ try {
84
+ if (entry.asset.endsWith(".tar.gz")) {
85
+ execSync(`tar xzf "${tmp}" -C "${CACHE_DIR}"`, { stdio: "pipe" })
86
+ unlinkSync(tmp)
87
+ } else if (entry.asset.endsWith(".zip")) {
88
+ if (os.platform() === "win32") {
89
+ // .NET ZipFile — built into .NET, no PowerShell module needed
90
+ const safeTmp = tmp.replace(/'/g, "''")
91
+ const safeDir = CACHE_DIR.replace(/'/g, "''")
92
+ execSync(
93
+ `powershell -Command "[System.Reflection.Assembly]::LoadWithPartialName('System.IO.Compression.FileSystem') | Out-Null; [System.IO.Compression.ZipFile]::ExtractToDirectory('${safeTmp}', '${safeDir}')"`,
94
+ { stdio: "pipe" },
95
+ )
96
+ } else {
97
+ execSync(`unzip -o "${tmp}" -d "${CACHE_DIR}"`, { stdio: "pipe" })
98
+ }
99
+ unlinkSync(tmp)
100
+ }
101
+ } catch (e) {
102
+ console.error(`arcana: extraction failed: ${e.message}`)
103
+ process.exit(1)
104
+ }
105
+
106
+ if (os.platform() !== "win32") {
107
+ try { chmodSync(CACHED_BINARY, 0o755) } catch {}
108
+ }
109
+
110
+ console.error(`arcana: ready ${CACHED_BINARY}`)
111
+ }
112
+
113
+ async function main() {
114
+ if (!existsSync(CACHED_BINARY)) {
115
+ await downloadAndExtract()
116
+ }
117
+
118
+ if (!existsSync(CACHED_BINARY)) {
119
+ console.error(`arcana: binary not found: ${CACHED_BINARY}`)
120
+ process.exit(1)
121
+ }
122
+
123
+ const args = process.argv.slice(2)
124
+ const child = spawnSync(CACHED_BINARY, args, { stdio: "inherit" })
125
+ process.exit(child.status ?? 0)
126
+ }
127
+
128
+ main().catch((err) => {
129
+ console.error(`arcana: ${err.message}`)
130
+ process.exit(1)
131
+ })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arcana-ai",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Self-improving AI agent CLI — opencode TUI + skills. Installs the arcana binary.",
5
5
  "bin": {
6
6
  "arcana": "./bin/arcana.js"
@@ -11,7 +11,16 @@
11
11
  ],
12
12
  "repository": "github:Lento47/arcana",
13
13
  "homepage": "https://github.com/Lento47/arcana",
14
- "keywords": ["ai", "agent", "cli", "tui", "opencode", "arcana", "llm", "coding-assistant"],
14
+ "keywords": [
15
+ "ai",
16
+ "agent",
17
+ "cli",
18
+ "tui",
19
+ "opencode",
20
+ "arcana",
21
+ "llm",
22
+ "coding-assistant"
23
+ ],
15
24
  "license": "MIT",
16
25
  "engines": {
17
26
  "node": ">=18"