lucentracode 0.1.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/LICENSE +21 -0
- package/README.md +48 -0
- package/bin/lucentracode.exe +11 -0
- package/package.json +52 -0
- package/postinstall.mjs +224 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 opencode
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# LucentraCode
|
|
2
|
+
|
|
3
|
+
AI coding agent for the terminal, powered by the LucentraCode gateway.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install -g lucentracode
|
|
7
|
+
lucentracode login # opens your browser to authorise this terminal
|
|
8
|
+
cd my-project && lucentracode
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
`login` opens the [LucentraCode dashboard](https://lucencode-dashboard.azurewebsites.net),
|
|
12
|
+
you click **Authorize**, and the CLI receives its own API key. You never type or
|
|
13
|
+
paste a credential.
|
|
14
|
+
|
|
15
|
+
## What you get
|
|
16
|
+
|
|
17
|
+
A terminal coding agent: repository exploration, grep and glob, file reads, edits
|
|
18
|
+
and patches, shell execution, test runs, tool calling with a permission prompt,
|
|
19
|
+
plan mode, subagents, context compaction, and MCP.
|
|
20
|
+
|
|
21
|
+
The default model is **Smart Auto**, which routes each request to a model chosen
|
|
22
|
+
for the task. `lucentracode models` lists the catalogue if you want to pin one.
|
|
23
|
+
|
|
24
|
+
## Usage and limits
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
lucentracode usage # allowance used, as a percentage
|
|
28
|
+
lucentracode status # account, gateway, plan
|
|
29
|
+
lucentracode dashboard # open the dashboard
|
|
30
|
+
lucentracode logout
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Install notes
|
|
34
|
+
|
|
35
|
+
This package contains no binary. On install it pulls the one
|
|
36
|
+
`lucentracode-<os>-<arch>` package that matches your machine, so you download one
|
|
37
|
+
platform's worth of bytes rather than all of them.
|
|
38
|
+
|
|
39
|
+
If you install with `--ignore-scripts` (or with pnpm, which defaults to that),
|
|
40
|
+
run the postinstall by hand:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
cd node_modules/lucentracode && node postinstall.mjs
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Licence and attribution
|
|
47
|
+
|
|
48
|
+
MIT. LucentraCode is a fork of [OpenCode](https://github.com/anomalyco/opencode).
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
echo "Error: lucentracode's postinstall script did not run." >&2
|
|
3
|
+
echo "" >&2
|
|
4
|
+
echo "This happens with --ignore-scripts, and with package managers such as pnpm" >&2
|
|
5
|
+
echo "that do not run postinstall scripts by default." >&2
|
|
6
|
+
echo "" >&2
|
|
7
|
+
echo "Fix it by running:" >&2
|
|
8
|
+
echo " cd node_modules/lucentracode && node postinstall.mjs" >&2
|
|
9
|
+
echo "" >&2
|
|
10
|
+
echo "Or reinstall without --ignore-scripts." >&2
|
|
11
|
+
exit 1
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "lucentracode",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "AI coding agent for the terminal, powered by the LucentraCode gateway.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/aryanbains/lucencode-cli.git"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/aryanbains/lucencode-cli",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/aryanbains/lucencode-cli/issues"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"ai",
|
|
16
|
+
"cli",
|
|
17
|
+
"coding-agent",
|
|
18
|
+
"terminal",
|
|
19
|
+
"tui",
|
|
20
|
+
"llm",
|
|
21
|
+
"lucentracode"
|
|
22
|
+
],
|
|
23
|
+
"bin": {
|
|
24
|
+
"lucentracode": "./bin/lucentracode.exe"
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"postinstall": "node ./postinstall.mjs"
|
|
28
|
+
},
|
|
29
|
+
"os": [
|
|
30
|
+
"darwin",
|
|
31
|
+
"linux",
|
|
32
|
+
"win32"
|
|
33
|
+
],
|
|
34
|
+
"cpu": [
|
|
35
|
+
"arm64",
|
|
36
|
+
"x64"
|
|
37
|
+
],
|
|
38
|
+
"optionalDependencies": {
|
|
39
|
+
"lucentracode-windows-x64-baseline": "0.1.0",
|
|
40
|
+
"lucentracode-windows-x64": "0.1.0",
|
|
41
|
+
"lucentracode-windows-arm64": "0.1.0",
|
|
42
|
+
"lucentracode-linux-x64-musl": "0.1.0",
|
|
43
|
+
"lucentracode-linux-x64-baseline-musl": "0.1.0",
|
|
44
|
+
"lucentracode-linux-x64-baseline": "0.1.0",
|
|
45
|
+
"lucentracode-linux-x64": "0.1.0",
|
|
46
|
+
"lucentracode-linux-arm64-musl": "0.1.0",
|
|
47
|
+
"lucentracode-linux-arm64": "0.1.0",
|
|
48
|
+
"lucentracode-darwin-x64-baseline": "0.1.0",
|
|
49
|
+
"lucentracode-darwin-x64": "0.1.0",
|
|
50
|
+
"lucentracode-darwin-arm64": "0.1.0"
|
|
51
|
+
}
|
|
52
|
+
}
|
package/postinstall.mjs
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Installed as `postinstall.mjs` inside the published `lucentracode` package.
|
|
4
|
+
//
|
|
5
|
+
// LucentraCode fork of script/postinstall.mjs. It is a separate file rather than an
|
|
6
|
+
// edit because the upstream one is still used by upstream's own release path, and
|
|
7
|
+
// because every identifier in here is a published package name -- a merge that
|
|
8
|
+
// silently reverted `lucentracode-linux-x64` to `opencode-linux-x64` would make our
|
|
9
|
+
// installer pull a different product's binary. Keeping them apart makes that
|
|
10
|
+
// impossible rather than merely unlikely.
|
|
11
|
+
//
|
|
12
|
+
// What it does: npm has already installed whichever `lucentracode-<platform>-<arch>`
|
|
13
|
+
// optional dependency matches this machine. This finds it and hard-links (or
|
|
14
|
+
// copies) the real binary over the placeholder shim at bin/lucentracode.exe.
|
|
15
|
+
|
|
16
|
+
import childProcess from "child_process"
|
|
17
|
+
import fs from "fs"
|
|
18
|
+
import os from "os"
|
|
19
|
+
import path from "path"
|
|
20
|
+
import { createRequire } from "module"
|
|
21
|
+
import { fileURLToPath } from "url"
|
|
22
|
+
|
|
23
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
24
|
+
const require = createRequire(import.meta.url)
|
|
25
|
+
const packageJson = JSON.parse(fs.readFileSync(path.join(__dirname, "package.json"), "utf8"))
|
|
26
|
+
|
|
27
|
+
const PRODUCT = "lucentracode"
|
|
28
|
+
|
|
29
|
+
const platformMap = {
|
|
30
|
+
darwin: "darwin",
|
|
31
|
+
linux: "linux",
|
|
32
|
+
win32: "windows",
|
|
33
|
+
}
|
|
34
|
+
const archMap = {
|
|
35
|
+
x64: "x64",
|
|
36
|
+
arm64: "arm64",
|
|
37
|
+
arm: "arm",
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const platform = platformMap[os.platform()] ?? os.platform()
|
|
41
|
+
const arch = archMap[os.arch()] ?? os.arch()
|
|
42
|
+
const base = `${PRODUCT}-${platform}-${arch}`
|
|
43
|
+
const sourceBinary = platform === "windows" ? `${PRODUCT}.exe` : PRODUCT
|
|
44
|
+
// Always `.exe`, on every platform. The published `bin` entry has to point at a
|
|
45
|
+
// path that exists before this script runs, and the same path afterwards; using one
|
|
46
|
+
// name everywhere means there is exactly one thing to replace. npm sets the
|
|
47
|
+
// executable bit from the bin field regardless of the extension.
|
|
48
|
+
const targetBinary = path.join(__dirname, "bin", `${PRODUCT}.exe`)
|
|
49
|
+
|
|
50
|
+
// AVX2 detection. Bun's default x64 build assumes AVX2; on a CPU without it the
|
|
51
|
+
// binary dies with an illegal instruction and no useful message, so pre-~2013
|
|
52
|
+
// hardware needs the `-baseline` variant instead.
|
|
53
|
+
function supportsAvx2() {
|
|
54
|
+
if (arch !== "x64") return false
|
|
55
|
+
|
|
56
|
+
if (platform === "linux") {
|
|
57
|
+
try {
|
|
58
|
+
return /(^|\s)avx2(\s|$)/i.test(fs.readFileSync("/proc/cpuinfo", "utf8"))
|
|
59
|
+
} catch {
|
|
60
|
+
return false
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (platform === "darwin") {
|
|
65
|
+
try {
|
|
66
|
+
const result = childProcess.spawnSync("sysctl", ["-n", "hw.optional.avx2_0"], {
|
|
67
|
+
encoding: "utf8",
|
|
68
|
+
timeout: 1500,
|
|
69
|
+
})
|
|
70
|
+
if (result.status !== 0) return false
|
|
71
|
+
return (result.stdout || "").trim() === "1"
|
|
72
|
+
} catch {
|
|
73
|
+
return false
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (platform === "windows") {
|
|
78
|
+
const command =
|
|
79
|
+
'(Add-Type -MemberDefinition "[DllImport(""kernel32.dll"")] public static extern bool IsProcessorFeaturePresent(int ProcessorFeature);" -Name Kernel32 -Namespace Win32 -PassThru)::IsProcessorFeaturePresent(40)'
|
|
80
|
+
|
|
81
|
+
for (const executable of ["powershell.exe", "pwsh.exe", "pwsh", "powershell"]) {
|
|
82
|
+
try {
|
|
83
|
+
const result = childProcess.spawnSync(executable, ["-NoProfile", "-NonInteractive", "-Command", command], {
|
|
84
|
+
encoding: "utf8",
|
|
85
|
+
timeout: 3000,
|
|
86
|
+
windowsHide: true,
|
|
87
|
+
})
|
|
88
|
+
if (result.status !== 0) continue
|
|
89
|
+
const output = (result.stdout || "").trim().toLowerCase()
|
|
90
|
+
if (output === "true" || output === "1") return true
|
|
91
|
+
if (output === "false" || output === "0") return false
|
|
92
|
+
} catch {
|
|
93
|
+
continue
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return false
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Alpine and other musl distros need a musl-linked binary; a glibc one fails at
|
|
102
|
+
// load time with an error that mentions neither libc nor LucentraCode.
|
|
103
|
+
function isMusl() {
|
|
104
|
+
if (platform !== "linux") return false
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
if (fs.existsSync("/etc/alpine-release")) return true
|
|
108
|
+
} catch {
|
|
109
|
+
// Ignore filesystem probes that are blocked by the host.
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
const result = childProcess.spawnSync("ldd", ["--version"], { encoding: "utf8" })
|
|
114
|
+
return `${result.stdout || ""}${result.stderr || ""}`.toLowerCase().includes("musl")
|
|
115
|
+
} catch {
|
|
116
|
+
return false
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Ordered best-first, with fallbacks. Detection can be wrong -- a container may
|
|
121
|
+
// hide /proc/cpuinfo, `ldd` may be absent -- so a wrong guess degrades to trying
|
|
122
|
+
// the next candidate instead of failing the install.
|
|
123
|
+
function packageNames() {
|
|
124
|
+
const baseline = arch === "x64" && !supportsAvx2()
|
|
125
|
+
|
|
126
|
+
if (platform === "linux") {
|
|
127
|
+
if (isMusl()) {
|
|
128
|
+
if (arch === "x64")
|
|
129
|
+
return baseline
|
|
130
|
+
? [`${base}-baseline-musl`, `${base}-musl`, `${base}-baseline`, base]
|
|
131
|
+
: [`${base}-musl`, `${base}-baseline-musl`, base, `${base}-baseline`]
|
|
132
|
+
return [`${base}-musl`, base]
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (arch === "x64")
|
|
136
|
+
return baseline
|
|
137
|
+
? [`${base}-baseline`, base, `${base}-baseline-musl`, `${base}-musl`]
|
|
138
|
+
: [base, `${base}-baseline`, `${base}-musl`, `${base}-baseline-musl`]
|
|
139
|
+
return [base, `${base}-musl`]
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (arch === "x64") return baseline ? [`${base}-baseline`, base] : [base, `${base}-baseline`]
|
|
143
|
+
return [base]
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function resolveBinary(name) {
|
|
147
|
+
const packageJsonPath = require.resolve(`${name}/package.json`)
|
|
148
|
+
const binaryPath = path.join(path.dirname(packageJsonPath), "bin", sourceBinary)
|
|
149
|
+
if (!fs.existsSync(binaryPath)) throw new Error(`Binary not found at ${binaryPath}`)
|
|
150
|
+
return binaryPath
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// pnpm and yarn PnP sometimes do not place optional dependencies where
|
|
154
|
+
// require.resolve can see them. Rather than fail, fetch the one package we need
|
|
155
|
+
// into a temp prefix and take the binary out of it.
|
|
156
|
+
function installPackage(name) {
|
|
157
|
+
const version = packageJson.optionalDependencies?.[name]
|
|
158
|
+
if (!version) return
|
|
159
|
+
|
|
160
|
+
const temp = fs.mkdtempSync(path.join(os.tmpdir(), `${PRODUCT}-install-`))
|
|
161
|
+
try {
|
|
162
|
+
const result = childProcess.spawnSync(
|
|
163
|
+
"npm",
|
|
164
|
+
["install", "--ignore-scripts", "--no-save", "--loglevel=error", "--prefix", temp, `${name}@${version}`],
|
|
165
|
+
{ stdio: "inherit", windowsHide: true },
|
|
166
|
+
)
|
|
167
|
+
if (result.status !== 0) return
|
|
168
|
+
const packageDir = path.join(temp, "node_modules", name)
|
|
169
|
+
copyBinary(path.join(packageDir, "bin", sourceBinary), targetBinary)
|
|
170
|
+
return true
|
|
171
|
+
} finally {
|
|
172
|
+
fs.rmSync(temp, { recursive: true, force: true })
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function copyBinary(source, target) {
|
|
177
|
+
if (!fs.existsSync(source)) throw new Error(`Binary not found at ${source}`)
|
|
178
|
+
fs.mkdirSync(path.dirname(target), { recursive: true })
|
|
179
|
+
if (fs.existsSync(target)) fs.unlinkSync(target)
|
|
180
|
+
try {
|
|
181
|
+
// A hard link avoids a second ~140 MB on disk. Falls back to a copy when the
|
|
182
|
+
// store and the project are on different filesystems.
|
|
183
|
+
fs.linkSync(source, target)
|
|
184
|
+
} catch {
|
|
185
|
+
fs.copyFileSync(source, target)
|
|
186
|
+
}
|
|
187
|
+
fs.chmodSync(target, 0o755)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Actually run it. A binary that is present but cannot execute -- wrong libc,
|
|
191
|
+
// wrong instruction set, truncated download -- must fall through to the next
|
|
192
|
+
// candidate rather than be declared installed.
|
|
193
|
+
function verifyBinary() {
|
|
194
|
+
const result = childProcess.spawnSync(targetBinary, ["--version"], {
|
|
195
|
+
encoding: "utf8",
|
|
196
|
+
stdio: "ignore",
|
|
197
|
+
windowsHide: true,
|
|
198
|
+
})
|
|
199
|
+
return result.status === 0
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function main() {
|
|
203
|
+
for (const name of packageNames()) {
|
|
204
|
+
try {
|
|
205
|
+
copyBinary(resolveBinary(name), targetBinary)
|
|
206
|
+
if (verifyBinary()) return
|
|
207
|
+
} catch {
|
|
208
|
+
if (installPackage(name) && verifyBinary()) return
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
throw new Error(
|
|
213
|
+
`Your package manager did not install a matching LucentraCode binary. Try installing ${packageNames()
|
|
214
|
+
.map((name) => JSON.stringify(name))
|
|
215
|
+
.join(" or ")} manually, or report this at https://github.com/aryanbains/lucencode-cli/issues with your OS and CPU architecture.`,
|
|
216
|
+
)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
try {
|
|
220
|
+
main()
|
|
221
|
+
} catch (error) {
|
|
222
|
+
console.error(error.message)
|
|
223
|
+
process.exit(1)
|
|
224
|
+
}
|