arcana-ai 0.1.2 → 0.1.4

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 (3) hide show
  1. package/README.md +12 -0
  2. package/bin/arcana.js +37 -31
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -20,3 +20,15 @@ Zero dependencies — the binary bundles the Bun runtime. Just download and run.
20
20
  ```bash
21
21
  npx arcana-ai@latest
22
22
  ```
23
+
24
+ ## Thanks
25
+
26
+ Arcana builds on open-source giants:
27
+
28
+ - **[OpenCode](https://github.com/anomalyco/opencode)** — TUI engine, provider system, tools, CLI architecture
29
+ - **[Hermes Agent](https://github.com/Lento47/hermes-agent)** — autonomous AI agent framework
30
+ - **[Bun](https://bun.sh)** — runtime + compiler producing the standalone binary
31
+ - **[models.dev](https://models.dev)** — community model catalog (200+ models, 33 providers)
32
+ - **[Effect](https://effect.website)** — typed functional effect system
33
+ - **[AI SDK](https://sdk.vercel.ai)** — unified LLM provider interface
34
+ - 174 community-contributed skills
package/bin/arcana.js CHANGED
@@ -1,21 +1,22 @@
1
1
  #!/usr/bin/env node
2
- // arcana launcher — downloads the opencode binary from GitHub releases if needed, then runs it.
3
- // This is the entrypoint for: npx arcana-ai
2
+ // arcana launcher — downloads the binary from GitHub releases if needed, then runs it.
3
+ // Entrypoint for: npx arcana-ai
4
4
  const { spawnSync, execSync } = require("child_process")
5
- const { existsSync, mkdirSync, chmodSync, writeFileSync, unlinkSync } = require("fs")
5
+ const { existsSync, mkdirSync, chmodSync, writeFileSync, unlinkSync, renameSync } = require("fs")
6
6
  const path = require("path")
7
7
  const os = require("os")
8
8
 
9
9
  const REPO = "anomalyco/opencode"
10
10
  const VERSION = "v1.17.8" // TODO: fetch latest via GitHub API
11
11
 
12
+ // Asset name = what GitHub release calls it. Binary name = what we rename it to.
12
13
  const PLATFORM_MAP = {
13
- "win32-x64": { name: "opencode-windows-x64.zip", binary: "opencode.exe" },
14
- "win32-arm64": { name: "opencode-windows-arm64.zip", binary: "opencode.exe" },
15
- "linux-x64": { name: "opencode-linux-x64.tar.gz", binary: "opencode" },
16
- "linux-arm64": { name: "opencode-linux-arm64.tar.gz", binary: "opencode" },
17
- "darwin-x64": { name: "opencode-darwin-x64.zip", binary: "opencode" },
18
- "darwin-arm64": { name: "opencode-darwin-arm64.zip", binary: "opencode" },
14
+ "win32-x64": { asset: "opencode-windows-x64.zip", binary: "arcana.exe" },
15
+ "win32-arm64": { asset: "opencode-windows-arm64.zip", binary: "arcana.exe" },
16
+ "linux-x64": { asset: "opencode-linux-x64.tar.gz", binary: "arcana" },
17
+ "linux-arm64": { asset: "opencode-linux-arm64.tar.gz", binary: "arcana" },
18
+ "darwin-x64": { asset: "opencode-darwin-x64.zip", binary: "arcana" },
19
+ "darwin-arm64": { asset: "opencode-darwin-arm64.zip", binary: "arcana" },
19
20
  }
20
21
 
21
22
  const platform = `${os.platform()}-${os.arch()}`
@@ -23,7 +24,7 @@ const entry = PLATFORM_MAP[platform]
23
24
 
24
25
  if (!entry) {
25
26
  console.error(`arcana: unsupported platform ${platform}`)
26
- console.error(`arcana: try installing from source: https://github.com/${REPO}`)
27
+ console.error(`arcana: try installing from source: https://github.com/Lento47/arcana`)
27
28
  process.exit(1)
28
29
  }
29
30
 
@@ -31,38 +32,40 @@ const CACHE_DIR = process.env.ARCANA_CACHE || path.join(os.homedir(), ".arcana",
31
32
  const CACHED_BINARY = path.join(CACHE_DIR, entry.binary)
32
33
 
33
34
  async function downloadAndExtract() {
34
- const url = `https://github.com/${REPO}/releases/download/${VERSION}/${entry.name}`
35
- console.error(`arcana: downloading ${entry.name}...`)
35
+ const ext = entry.asset.endsWith(".tar.gz") ? ".tar.gz" : ".zip"
36
+ const zipName = `arcana-${platform}${ext}`
37
+
38
+ // Clean up any stale temp files from previous failed attempts
39
+ try { unlinkSync(path.join(CACHE_DIR, zipName)) } catch {}
40
+ try { unlinkSync(path.join(CACHE_DIR, entry.asset)) } catch {}
41
+
42
+ const url = `https://github.com/${REPO}/releases/download/${VERSION}/${entry.asset}`
43
+ console.error(`arcana: downloading ${zipName}...`)
36
44
 
37
45
  mkdirSync(CACHE_DIR, { recursive: true })
38
46
 
39
- // Download via Node.js fetch
40
47
  const res = await fetch(url)
41
48
  if (!res.ok) {
42
49
  console.error(`arcana: download failed: ${res.status} ${res.statusText}`)
43
- console.error(`arcana: URL: ${url}`)
44
50
  process.exit(1)
45
51
  }
46
52
 
47
- // Clean up stale temp files from previous failed attempts
48
- try { unlinkSync(path.join(CACHE_DIR, entry.name)) } catch {}
49
-
50
- // Write to temp file
51
- const tmp = path.join(CACHE_DIR, entry.name)
53
+ const tmp = path.join(CACHE_DIR, zipName)
52
54
  const buf = Buffer.from(await res.arrayBuffer())
53
55
  writeFileSync(tmp, buf)
54
- console.error(`arcana: downloaded ${(buf.length / 1e6).toFixed(1)}MB`)
56
+ console.error(`arcana: ${(buf.length / 1e6).toFixed(1)}MB, extracting...`)
55
57
 
56
- // Extract
57
58
  try {
58
- if (entry.name.endsWith(".tar.gz")) {
59
+ if (entry.asset.endsWith(".tar.gz")) {
59
60
  execSync(`tar xzf "${tmp}" -C "${CACHE_DIR}"`, { stdio: "pipe" })
60
61
  unlinkSync(tmp)
61
- } else if (entry.name.endsWith(".zip")) {
62
+ } else if (entry.asset.endsWith(".zip")) {
62
63
  if (os.platform() === "win32") {
63
- // Use .NET ZipFile (built into .NET, no PowerShell module needed)
64
+ // .NET ZipFile built into .NET, no PowerShell module needed
65
+ const safeTmp = tmp.replace(/'/g, "''")
66
+ const safeDir = CACHE_DIR.replace(/'/g, "''")
64
67
  execSync(
65
- `powershell -Command "[System.Reflection.Assembly]::LoadWithPartialName('System.IO.Compression.FileSystem') | Out-Null; [System.IO.Compression.ZipFile]::ExtractToDirectory('${tmp.replace(/'/g, "''")}', '${CACHE_DIR.replace(/'/g, "''")}')"`,
68
+ `powershell -Command "[System.Reflection.Assembly]::LoadWithPartialName('System.IO.Compression.FileSystem') | Out-Null; [System.IO.Compression.ZipFile]::ExtractToDirectory('${safeTmp}', '${safeDir}')"`,
66
69
  { stdio: "pipe" },
67
70
  )
68
71
  } else {
@@ -72,30 +75,33 @@ async function downloadAndExtract() {
72
75
  }
73
76
  } catch (e) {
74
77
  console.error(`arcana: extraction failed: ${e.message}`)
75
- // Leave tmp file for manual debugging
76
78
  process.exit(1)
77
79
  }
78
80
 
79
- // Ensure executable
81
+ // Rename extracted binary from opencode → arcana
82
+ const extracted = path.join(CACHE_DIR, os.platform() === "win32" ? "opencode.exe" : "opencode")
83
+ if (existsSync(extracted) && extracted !== CACHED_BINARY) {
84
+ try { unlinkSync(CACHED_BINARY) } catch {}
85
+ renameSync(extracted, CACHED_BINARY)
86
+ }
87
+
80
88
  if (os.platform() !== "win32") {
81
89
  try { chmodSync(CACHED_BINARY, 0o755) } catch {}
82
90
  }
83
91
 
84
- console.error(`arcana: installed to ${CACHED_BINARY}`)
92
+ console.error(`arcana: ready ${CACHED_BINARY}`)
85
93
  }
86
94
 
87
95
  async function main() {
88
- // Download if needed
89
96
  if (!existsSync(CACHED_BINARY)) {
90
97
  await downloadAndExtract()
91
98
  }
92
99
 
93
100
  if (!existsSync(CACHED_BINARY)) {
94
- console.error(`arcana: binary not found after download: ${CACHED_BINARY}`)
101
+ console.error(`arcana: binary not found: ${CACHED_BINARY}`)
95
102
  process.exit(1)
96
103
  }
97
104
 
98
- // Run binary with same args
99
105
  const args = process.argv.slice(2)
100
106
  const child = spawnSync(CACHED_BINARY, args, { stdio: "inherit" })
101
107
  process.exit(child.status ?? 0)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arcana-ai",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Self-improving AI agent CLI — opencode TUI + skills. Installs the arcana binary.",
5
5
  "bin": {
6
6
  "arcana": "./bin/arcana.js"