karajan-code 3.8.0 → 3.10.1
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 +32 -1
- package/package.json +1 -1
- package/scripts/build-sea.mjs +6 -0
- package/scripts/esbuild-sea.config.mjs +9 -0
- package/scripts/install-binary.ps1 +89 -0
- package/scripts/install-binary.sh +121 -0
- package/src/commands/doctor.js +25 -1
- package/src/utils/update-check.js +38 -1
package/README.md
CHANGED
|
@@ -112,6 +112,16 @@ brew install manufosela/tap/karajan-code
|
|
|
112
112
|
|
|
113
113
|
> **Installing with pnpm?** pnpm blocks dependency build scripts by default, so `better-sqlite3`'s native addon won't compile on install and DB-backed commands (`board`, `rag`, cost tracking) will fail. After installing, run `pnpm approve-builds better-sqlite3` — or just use npm. `kj doctor` flags this if it happens.
|
|
114
114
|
|
|
115
|
+
> **On a bare Linux box, install `curl` first.** The binary and script installers
|
|
116
|
+
> below download with `curl` (or `wget`). macOS already ships `curl` and Windows
|
|
117
|
+
> PowerShell has `irm` built in, but a minimal server or container image often has
|
|
118
|
+
> neither — install it with your package manager, then run the one-liner:
|
|
119
|
+
> - **Debian/Ubuntu**: `sudo apt update && sudo apt install -y curl`
|
|
120
|
+
> - **Fedora/RHEL/CentOS**: `sudo dnf install -y curl`
|
|
121
|
+
> - **Arch**: `sudo pacman -S --noconfirm curl`
|
|
122
|
+
> - **Alpine**: `sudo apk add curl`
|
|
123
|
+
> - **openSUSE**: `sudo zypper install -y curl`
|
|
124
|
+
|
|
115
125
|
**Standalone binary** (no Node.js needed):
|
|
116
126
|
```bash
|
|
117
127
|
# macOS (Apple Silicon)
|
|
@@ -124,7 +134,28 @@ curl -L https://github.com/manufosela/karajan-code/releases/latest/download/kj-l
|
|
|
124
134
|
curl -L https://github.com/manufosela/karajan-code/releases/latest/download/kj-win-x64.exe -o kj.exe
|
|
125
135
|
```
|
|
126
136
|
|
|
127
|
-
**One-liner** (detects OS, installs
|
|
137
|
+
**One-liner, binary** (no Node — detects OS/arch, verifies the checksum, installs to `~/.local/bin`):
|
|
138
|
+
```bash
|
|
139
|
+
curl -fsSL https://raw.githubusercontent.com/manufosela/karajan-code/main/scripts/install-binary.sh | sh
|
|
140
|
+
```
|
|
141
|
+
Pin a version or install dir with env vars: `KJ_VERSION=v3.7.2 KJ_INSTALL_DIR=/usr/local/bin`.
|
|
142
|
+
|
|
143
|
+
**One-liner, binary — Windows** (no Node — verifies the checksum, installs to `%LOCALAPPDATA%\Karajan` and adds it to your user PATH):
|
|
144
|
+
```powershell
|
|
145
|
+
irm https://raw.githubusercontent.com/manufosela/karajan-code/main/scripts/install-binary.ps1 | iex
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
> **If your OS blocks the binary.** The macOS build is ad-hoc signed and the
|
|
149
|
+
> Windows build is unsigned — neither carries a paid certificate, so a manually
|
|
150
|
+
> downloaded binary may be blocked on first run. The install scripts above clear
|
|
151
|
+
> the flag for you. If you download the binary by hand instead:
|
|
152
|
+
> - **macOS** — Gatekeeper says the binary "cannot be opened": `xattr -d com.apple.quarantine ./kj`, or Control-click the file → **Open**.
|
|
153
|
+
> - **Windows** — SmartScreen shows "Windows protected your PC": click **More info → Run anyway**, or run `Unblock-File .\kj.exe`.
|
|
154
|
+
>
|
|
155
|
+
> Paid notarization (Apple) and Authenticode signing (Windows) are optional and
|
|
156
|
+
> not enabled by default — this is a deliberate cost decision, not an oversight.
|
|
157
|
+
|
|
158
|
+
**One-liner, npm** (detects OS, installs via npm — needs Node ≥ 18):
|
|
128
159
|
```bash
|
|
129
160
|
curl -fsSL https://raw.githubusercontent.com/manufosela/karajan-code/main/scripts/install-kj.sh | sh
|
|
130
161
|
```
|
package/package.json
CHANGED
package/scripts/build-sea.mjs
CHANGED
|
@@ -85,10 +85,16 @@ async function main() {
|
|
|
85
85
|
|
|
86
86
|
// ── Step 4: Inject blob with postject ───────────────────────────
|
|
87
87
|
console.log("[4/5] Injecting SEA blob with postject...");
|
|
88
|
+
// On macOS the blob MUST land as section NODE_SEA_BLOB inside a segment
|
|
89
|
+
// named NODE_SEA. Without --macho-segment-name postject writes it to a
|
|
90
|
+
// default segment the SEA loader never inspects, so the binary segfaults
|
|
91
|
+
// on startup (KJC-BUG-0096, nodejs/postject#76). The flag is Mach-O only —
|
|
92
|
+
// ELF (Linux) and PE (Windows) don't take it — so add it only on macOS.
|
|
88
93
|
const postjectCmd = [
|
|
89
94
|
`npx postject ${binaryPath}`,
|
|
90
95
|
"NODE_SEA_BLOB dist/sea-prep.blob",
|
|
91
96
|
"--sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2",
|
|
97
|
+
...(isMac ? ["--macho-segment-name NODE_SEA"] : []),
|
|
92
98
|
].join(" ");
|
|
93
99
|
run(postjectCmd);
|
|
94
100
|
console.log(" -> Blob injected.\n");
|
|
@@ -183,6 +183,15 @@ const ragStubPlugin = {
|
|
|
183
183
|
parseWhere: notAvailable, buildWhereSql: notAvailable,
|
|
184
184
|
// KJC-TSK-0449 — RAG cross-encoder rerank.
|
|
185
185
|
rerank: notAvailable, RerankError: notAvailable, _resetPipeline: notAvailable,
|
|
186
|
+
// KJC-BUG-0097 — src/rag/auto-update.js lives under src/rag/ so it is
|
|
187
|
+
// caught by this stub, but its exports are called on the hot path.
|
|
188
|
+
// maybeAutoUpdate() runs on EVERY \`kj run\` (run.js): it must be a
|
|
189
|
+
// silent no-op here (RAG index is unavailable in SEA anyway), NOT an
|
|
190
|
+
// absent symbol — otherwise the whole command crashes with
|
|
191
|
+
// "maybeAutoUpdate is not a function". installPostMergeHook() is only
|
|
192
|
+
// reached from \`kj rag install-hooks\`, so it degrades like the rest.
|
|
193
|
+
maybeAutoUpdate: async () => ({ skipped: true }),
|
|
194
|
+
installPostMergeHook: notAvailable,
|
|
186
195
|
default: notAvailable,
|
|
187
196
|
};
|
|
188
197
|
`,
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Karajan Code — standalone binary installer for Windows (no Node required).
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
Downloads the prebuilt kj.exe for Windows x64 from the GitHub release,
|
|
7
|
+
verifies its SHA256 checksum, installs it to %LOCALAPPDATA%\Karajan, and
|
|
8
|
+
adds that folder to the user PATH (idempotently). Re-running updates the
|
|
9
|
+
binary in place without duplicating PATH entries.
|
|
10
|
+
|
|
11
|
+
Run it with:
|
|
12
|
+
irm https://karajancode.com/install.ps1 | iex
|
|
13
|
+
|
|
14
|
+
Override with env vars: $env:KJ_VERSION (e.g. v3.7.2), $env:KJ_INSTALL_DIR.
|
|
15
|
+
|
|
16
|
+
KJC-TSK-0594.
|
|
17
|
+
#>
|
|
18
|
+
$ErrorActionPreference = "Stop"
|
|
19
|
+
|
|
20
|
+
$repo = "manufosela/karajan-code"
|
|
21
|
+
$version = if ($env:KJ_VERSION) { $env:KJ_VERSION } else { "latest" }
|
|
22
|
+
$installDir = if ($env:KJ_INSTALL_DIR) { $env:KJ_INSTALL_DIR } else { Join-Path $env:LOCALAPPDATA "Karajan" }
|
|
23
|
+
$asset = "kj-win-x64.exe"
|
|
24
|
+
|
|
25
|
+
if ($version -eq "latest") {
|
|
26
|
+
$base = "https://github.com/$repo/releases/latest/download"
|
|
27
|
+
} else {
|
|
28
|
+
$base = "https://github.com/$repo/releases/download/$version"
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
# Download to a temp folder; only install once the checksum matches, so a
|
|
32
|
+
# failure never leaves a half-installed binary behind.
|
|
33
|
+
$tmp = Join-Path ([System.IO.Path]::GetTempPath()) ("kj-install-" + [System.Guid]::NewGuid().ToString("N"))
|
|
34
|
+
New-Item -ItemType Directory -Path $tmp -Force | Out-Null
|
|
35
|
+
try {
|
|
36
|
+
$binTmp = Join-Path $tmp "kj.exe"
|
|
37
|
+
$shaTmp = Join-Path $tmp "kj.exe.sha256"
|
|
38
|
+
|
|
39
|
+
Write-Host "kj-install: downloading $asset ($version)..."
|
|
40
|
+
try {
|
|
41
|
+
Invoke-WebRequest -Uri "$base/$asset" -OutFile $binTmp -UseBasicParsing
|
|
42
|
+
Invoke-WebRequest -Uri "$base/$asset.sha256" -OutFile $shaTmp -UseBasicParsing
|
|
43
|
+
} catch {
|
|
44
|
+
throw "could not download $base/$asset — does that version/asset exist, and is the network reachable? ($_)"
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
# certutil writes a multi-line file; keep only the hex of the hash line.
|
|
48
|
+
$shaLine = (Get-Content $shaTmp | Where-Object { $_ -notmatch "SHA256|CertUtil" } | Select-Object -First 1)
|
|
49
|
+
$expected = ($shaLine -replace "[^0-9A-Fa-f]", "").ToLower()
|
|
50
|
+
$actual = (Get-FileHash -Algorithm SHA256 -Path $binTmp).Hash.ToLower()
|
|
51
|
+
if ($expected.Length -ne 64 -or $expected -ne $actual) {
|
|
52
|
+
throw "checksum mismatch (expected '$expected', got '$actual'). Aborting, nothing installed."
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# Install: move into place, replacing any previous install (idempotent).
|
|
56
|
+
New-Item -ItemType Directory -Path $installDir -Force | Out-Null
|
|
57
|
+
$dest = Join-Path $installDir "kj.exe"
|
|
58
|
+
Move-Item -Path $binTmp -Destination $dest -Force
|
|
59
|
+
|
|
60
|
+
# The download carries a "mark of the web" (Zone.Identifier) that makes
|
|
61
|
+
# SmartScreen warn on first run. The binary is not code-signed with a paid
|
|
62
|
+
# certificate, so clear the mark on the copy we just verified ourselves.
|
|
63
|
+
Unblock-File -Path $dest -ErrorAction SilentlyContinue
|
|
64
|
+
|
|
65
|
+
$installed = (& $dest --version) 2>$null
|
|
66
|
+
Write-Host "kj-install: installed kj $installed to $dest"
|
|
67
|
+
|
|
68
|
+
# Add to the user PATH only if it is not already there (no duplicates).
|
|
69
|
+
$userPath = [Environment]::GetEnvironmentVariable("Path", "User")
|
|
70
|
+
$parts = @()
|
|
71
|
+
if ($userPath) { $parts = $userPath.Split(";") | Where-Object { $_ -ne "" } }
|
|
72
|
+
$already = $parts | Where-Object { $_.TrimEnd("\") -ieq $installDir.TrimEnd("\") }
|
|
73
|
+
if (-not $already) {
|
|
74
|
+
$newPath = if ($userPath) { "$userPath;$installDir" } else { $installDir }
|
|
75
|
+
[Environment]::SetEnvironmentVariable("Path", $newPath, "User")
|
|
76
|
+
Write-Host "kj-install: added '$installDir' to your user PATH. Open a new terminal to use 'kj'."
|
|
77
|
+
} else {
|
|
78
|
+
Write-Host "kj-install: '$installDir' is already on your user PATH — run 'kj --help' to get started."
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
# The binary bundles no toolchain. kj orchestrates external tools, so name
|
|
82
|
+
# the hard requirements and let `kj doctor` check them for this machine.
|
|
83
|
+
Write-Host ""
|
|
84
|
+
Write-Host "kj-install: next step — run 'kj doctor' to check prerequisites."
|
|
85
|
+
Write-Host " Required: git, plus at least one agent CLI (Claude Code, Codex or Gemini)."
|
|
86
|
+
Write-Host " Optional: Docker (local models, SonarQube) and Node/npm (helper tools: Squeezr, qmd)."
|
|
87
|
+
} finally {
|
|
88
|
+
Remove-Item -Path $tmp -Recurse -Force -ErrorAction SilentlyContinue
|
|
89
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Karajan Code — standalone binary installer (no Node required).
|
|
3
|
+
#
|
|
4
|
+
# curl -fsSL https://karajancode.com/install.sh | sh
|
|
5
|
+
#
|
|
6
|
+
# Downloads the prebuilt `kj` binary for your OS/arch from the GitHub
|
|
7
|
+
# release, verifies its SHA256 checksum, and installs it to ~/.local/bin.
|
|
8
|
+
# Override with env vars: KJ_VERSION (e.g. v3.7.2), KJ_INSTALL_DIR.
|
|
9
|
+
#
|
|
10
|
+
# POSIX sh only — no bashisms, no Node. KJC-TSK-0593.
|
|
11
|
+
set -eu
|
|
12
|
+
|
|
13
|
+
REPO="manufosela/karajan-code"
|
|
14
|
+
VERSION="${KJ_VERSION:-latest}"
|
|
15
|
+
INSTALL_DIR="${KJ_INSTALL_DIR:-$HOME/.local/bin}"
|
|
16
|
+
|
|
17
|
+
die() {
|
|
18
|
+
echo "kj-install: $1" >&2
|
|
19
|
+
exit 1
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
# --- Detect OS/arch, map to the release asset names we actually build. ---
|
|
23
|
+
os="$(uname -s)"
|
|
24
|
+
arch="$(uname -m)"
|
|
25
|
+
case "$os" in
|
|
26
|
+
Linux) os="linux" ;;
|
|
27
|
+
Darwin) os="darwin" ;;
|
|
28
|
+
*) die "unsupported OS '$os'. Supported: Linux, macOS. Use npm instead: npm install -g karajan-code" ;;
|
|
29
|
+
esac
|
|
30
|
+
case "$arch" in
|
|
31
|
+
x86_64 | amd64) arch="x64" ;;
|
|
32
|
+
arm64 | aarch64) arch="arm64" ;;
|
|
33
|
+
*) die "unsupported architecture '$arch'" ;;
|
|
34
|
+
esac
|
|
35
|
+
|
|
36
|
+
target="${os}-${arch}"
|
|
37
|
+
case "$target" in
|
|
38
|
+
linux-x64) ;;
|
|
39
|
+
darwin-arm64)
|
|
40
|
+
# The macOS standalone binary is not published yet (tracked in
|
|
41
|
+
# KJC-BUG-0096: the darwin-arm64 SEA build crashes under smoke-test).
|
|
42
|
+
# Degrade gracefully to the npm install path instead of trying to
|
|
43
|
+
# download an asset that does not exist.
|
|
44
|
+
echo "kj-install: the macOS standalone binary is not available yet." >&2
|
|
45
|
+
echo "kj-install: install with npm instead (requires Node 18+):" >&2
|
|
46
|
+
echo " npm install -g karajan-code" >&2
|
|
47
|
+
exit 0
|
|
48
|
+
;;
|
|
49
|
+
*) die "no prebuilt binary for '$target'. Available: linux-x64. On macOS use npm instead: npm install -g karajan-code" ;;
|
|
50
|
+
esac
|
|
51
|
+
|
|
52
|
+
# --- Resolve the download URL for the requested version (or latest). ---
|
|
53
|
+
asset="kj-${target}"
|
|
54
|
+
if [ "$VERSION" = "latest" ]; then
|
|
55
|
+
base="https://github.com/${REPO}/releases/latest/download"
|
|
56
|
+
else
|
|
57
|
+
base="https://github.com/${REPO}/releases/download/${VERSION}"
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
# --- Pick a downloader. ---
|
|
61
|
+
if command -v curl >/dev/null 2>&1; then
|
|
62
|
+
fetch() { curl -fsSL "$1" -o "$2"; }
|
|
63
|
+
elif command -v wget >/dev/null 2>&1; then
|
|
64
|
+
fetch() { wget -qO "$2" "$1"; }
|
|
65
|
+
else
|
|
66
|
+
die "need curl or wget to download the binary"
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
# --- Pick a checksum tool. ---
|
|
70
|
+
if command -v sha256sum >/dev/null 2>&1; then
|
|
71
|
+
sha256() { sha256sum "$1" | cut -d' ' -f1; }
|
|
72
|
+
elif command -v shasum >/dev/null 2>&1; then
|
|
73
|
+
sha256() { shasum -a 256 "$1" | cut -d' ' -f1; }
|
|
74
|
+
else
|
|
75
|
+
die "need sha256sum or shasum to verify the download"
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
# --- Download to a temp dir; only install after the checksum matches. ---
|
|
79
|
+
tmp="$(mktemp -d "${TMPDIR:-/tmp}/kj-install.XXXXXX")"
|
|
80
|
+
trap 'rm -rf "$tmp"' EXIT INT TERM
|
|
81
|
+
|
|
82
|
+
echo "kj-install: downloading ${asset} (${VERSION})..."
|
|
83
|
+
fetch "${base}/${asset}" "${tmp}/kj" || die "could not download ${base}/${asset} — does that version/asset exist?"
|
|
84
|
+
fetch "${base}/${asset}.sha256" "${tmp}/kj.sha256" || die "could not download the checksum for ${asset}"
|
|
85
|
+
|
|
86
|
+
expected="$(cut -d' ' -f1 <"${tmp}/kj.sha256")"
|
|
87
|
+
actual="$(sha256 "${tmp}/kj")"
|
|
88
|
+
[ "$expected" = "$actual" ] || die "checksum mismatch (expected ${expected}, got ${actual}). Aborting, nothing installed."
|
|
89
|
+
|
|
90
|
+
# --- Install atomically: chmod on the temp file, then move into place. ---
|
|
91
|
+
chmod +x "${tmp}/kj"
|
|
92
|
+
mkdir -p "$INSTALL_DIR"
|
|
93
|
+
mv -f "${tmp}/kj" "${INSTALL_DIR}/kj"
|
|
94
|
+
|
|
95
|
+
# On macOS the binary is ad-hoc signed, not notarized with a paid Apple
|
|
96
|
+
# certificate, so Gatekeeper can quarantine it. Clear the flag on the copy
|
|
97
|
+
# we just checksummed ourselves (no-op on Linux / if the flag is absent).
|
|
98
|
+
if [ "$os" = "darwin" ] && command -v xattr >/dev/null 2>&1; then
|
|
99
|
+
xattr -d com.apple.quarantine "${INSTALL_DIR}/kj" 2>/dev/null || true
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
installed="$("${INSTALL_DIR}/kj" --version 2>/dev/null || echo '?')"
|
|
103
|
+
echo "kj-install: installed kj ${installed} to ${INSTALL_DIR}/kj"
|
|
104
|
+
|
|
105
|
+
# --- Tell the user how to reach it if the dir is not on PATH. ---
|
|
106
|
+
case ":${PATH}:" in
|
|
107
|
+
*":${INSTALL_DIR}:"*) echo "kj-install: '${INSTALL_DIR}' is on your PATH — run 'kj --help' to get started." ;;
|
|
108
|
+
*)
|
|
109
|
+
echo "kj-install: '${INSTALL_DIR}' is not on your PATH. Add it with:"
|
|
110
|
+
echo " export PATH=\"${INSTALL_DIR}:\$PATH\""
|
|
111
|
+
echo " (add that line to ~/.bashrc, ~/.zshrc or ~/.profile to make it permanent)"
|
|
112
|
+
;;
|
|
113
|
+
esac
|
|
114
|
+
|
|
115
|
+
# --- Prerequisites: the binary bundles no toolchain. kj orchestrates ---
|
|
116
|
+
# --- external tools, so name the hard requirements and let `kj doctor` ---
|
|
117
|
+
# --- check them precisely for this machine.
|
|
118
|
+
echo ""
|
|
119
|
+
echo "kj-install: next step — run 'kj doctor' to check prerequisites."
|
|
120
|
+
echo " Required: git, plus at least one agent CLI (Claude Code, Codex or Gemini)."
|
|
121
|
+
echo " Optional: Docker (local models, SonarQube) and Node/npm (helper tools: Squeezr, qmd)."
|
package/src/commands/doctor.js
CHANGED
|
@@ -160,6 +160,28 @@ function statusIcon(status) {
|
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
+
// The `kj` binary bundles no toolchain: it orchestrates external tools. So
|
|
164
|
+
// beyond the per-check wall, spell out what is *required* to run kj at all
|
|
165
|
+
// (git + at least one agent CLI) versus what is *optional* (enables extra
|
|
166
|
+
// features). We derive the verdict from the checks already collected rather
|
|
167
|
+
// than re-probing, so it always agrees with the lines printed above.
|
|
168
|
+
function printPrerequisites(report) {
|
|
169
|
+
const git = report.checks.find((c) => c.name === "git");
|
|
170
|
+
const agents = report.checks.filter((c) => c.name?.startsWith("agent:"));
|
|
171
|
+
const okAgent = agents.find((c) => c.status === STATUS.OK);
|
|
172
|
+
const mark = (ok) => (ok ? "OK" : "MISSING");
|
|
173
|
+
|
|
174
|
+
const gitOk = git?.status === STATUS.OK;
|
|
175
|
+
const agentLabel = okAgent
|
|
176
|
+
? `OK (${okAgent.label.replace(/^Agent:\s*/, "")})`
|
|
177
|
+
: "MISSING — install Claude Code, Codex or Gemini";
|
|
178
|
+
|
|
179
|
+
console.log();
|
|
180
|
+
console.log("Prerequisites (kj orchestrates external tools, it bundles none):");
|
|
181
|
+
console.log(` Required: git ${mark(gitOk)}; agent CLI ${agentLabel}`);
|
|
182
|
+
console.log(" Optional: Docker (local models, SonarQube); Node/npm (helper tools: Squeezr, qmd)");
|
|
183
|
+
}
|
|
184
|
+
|
|
163
185
|
function printHuman(report, { verbose }) {
|
|
164
186
|
for (const check of report.checks) {
|
|
165
187
|
console.log(`${statusIcon(check.status)} ${check.label}: ${check.detail}`);
|
|
@@ -186,6 +208,8 @@ function printHuman(report, { verbose }) {
|
|
|
186
208
|
console.log(`Runtime overrides applied: ${JSON.stringify(report.overrides)}`);
|
|
187
209
|
}
|
|
188
210
|
|
|
211
|
+
printPrerequisites(report);
|
|
212
|
+
|
|
189
213
|
// KJC-TSK v2.18 — if any external audit tool (semgrep, osv-scanner,
|
|
190
214
|
// lighthouse) reported missing, surface the one-line remediation so
|
|
191
215
|
// the user doesn't have to dig the fix lines out of the wall of
|
|
@@ -202,4 +226,4 @@ function printHuman(report, { verbose }) {
|
|
|
202
226
|
}
|
|
203
227
|
}
|
|
204
228
|
|
|
205
|
-
export const __test = { printHuman };
|
|
229
|
+
export const __test = { printHuman, printPrerequisites };
|
|
@@ -1,11 +1,47 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { isSea } from "node:sea";
|
|
3
4
|
import { getKarajanHome } from "./paths.js";
|
|
4
5
|
|
|
5
6
|
const CACHE_FILE = "update-check.json";
|
|
6
7
|
const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
7
8
|
const PACKAGE_NAME = "karajan-code";
|
|
8
9
|
|
|
10
|
+
const RAW_BASE = "https://raw.githubusercontent.com/manufosela/karajan-code/main/scripts";
|
|
11
|
+
export const INSTALL_SH_URL = `${RAW_BASE}/install-binary.sh`;
|
|
12
|
+
export const INSTALL_PS1_URL = `${RAW_BASE}/install-binary.ps1`;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Build the "how to update" line for the detected install channel.
|
|
16
|
+
* Pure and testable — no I/O.
|
|
17
|
+
* @param {{channel: "sea"|"npm"|"unknown", platform?: string}} opts
|
|
18
|
+
*/
|
|
19
|
+
export function updateInstruction({ channel, platform = process.platform }) {
|
|
20
|
+
if (channel === "sea") {
|
|
21
|
+
return platform === "win32"
|
|
22
|
+
? `Re-run the installer: irm ${INSTALL_PS1_URL} | iex`
|
|
23
|
+
: `Re-run the installer: curl -fsSL ${INSTALL_SH_URL} | sh`;
|
|
24
|
+
}
|
|
25
|
+
if (channel === "npm") {
|
|
26
|
+
return `Run: npm install -g ${PACKAGE_NAME}`;
|
|
27
|
+
}
|
|
28
|
+
// Channel unknown — offer both paths, never silently pick a wrong one.
|
|
29
|
+
return `Update: npm install -g ${PACKAGE_NAME} (or re-run the binary installer — see README)`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Detect how this kj was installed: "sea" (standalone binary) or "npm"
|
|
34
|
+
* (global install / source tree). Falls back to "npm" if isSea() throws.
|
|
35
|
+
*/
|
|
36
|
+
export function detectInstallChannel() {
|
|
37
|
+
try {
|
|
38
|
+
return isSea() ? "sea" : "npm";
|
|
39
|
+
} catch {
|
|
40
|
+
// isSea() unexpectedly threw ⇒ treat as npm / source tree, never block.
|
|
41
|
+
return "npm";
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
9
45
|
/**
|
|
10
46
|
* Check npm for a newer version. Non-blocking, cached for 24h.
|
|
11
47
|
* Returns { updateAvailable, latest, current } or null if check fails/cached.
|
|
@@ -61,8 +97,9 @@ export async function checkForUpdate(currentVersion) {
|
|
|
61
97
|
export async function printUpdateNotice(currentVersion) {
|
|
62
98
|
const result = await checkForUpdate(currentVersion);
|
|
63
99
|
if (result?.updateAvailable) {
|
|
100
|
+
const channel = detectInstallChannel();
|
|
64
101
|
console.log(`\n Update available: v${result.current} → v${result.latest}`);
|
|
65
|
-
console.log(`
|
|
102
|
+
console.log(` ${updateInstruction({ channel })}\n`);
|
|
66
103
|
}
|
|
67
104
|
}
|
|
68
105
|
|