karajan-code 4.1.1 → 4.1.2
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/package.json +1 -1
- package/scripts/install-binary.sh +119 -72
- package/src/commands/install-tools.js +27 -1
package/package.json
CHANGED
|
@@ -1,31 +1,36 @@
|
|
|
1
1
|
#!/bin/sh
|
|
2
|
-
# Karajan Code —
|
|
2
|
+
# Karajan Code installer — guarantees a COMPLETE install (KJC-TSK-0658).
|
|
3
3
|
#
|
|
4
4
|
# curl -fsSL https://karajancode.com/install.sh | sh
|
|
5
5
|
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
6
|
+
# Default route: npm — the full product (CLI + RAG + HU Board + MCP).
|
|
7
|
+
# 1. Node >= 22.12 present → npm install -g karajan-code
|
|
8
|
+
# 2. No usable Node → auto-provision official Node LTS into
|
|
9
|
+
# ~/.karajan/node (checksum-verified, nothing system-wide touched),
|
|
10
|
+
# install karajan-code with it, symlink kj into ~/.local/bin.
|
|
11
|
+
# Standalone route (CLI only, no native-module features — RAG/board/MCP
|
|
12
|
+
# unavailable): opt-in with `--standalone` or KJ_STANDALONE=1.
|
|
9
13
|
#
|
|
10
|
-
# POSIX sh only
|
|
14
|
+
# POSIX sh only. Env overrides: KJ_VERSION, KJ_INSTALL_DIR.
|
|
11
15
|
set -eu
|
|
12
16
|
|
|
13
17
|
REPO="manufosela/karajan-code"
|
|
14
18
|
VERSION="${KJ_VERSION:-latest}"
|
|
15
19
|
INSTALL_DIR="${KJ_INSTALL_DIR:-$HOME/.local/bin}"
|
|
20
|
+
NODE_MAJOR="22"
|
|
21
|
+
NODE_MIN_MINOR="12"
|
|
22
|
+
MODE="full"
|
|
23
|
+
[ "${1:-}" = "--standalone" ] && MODE="standalone"
|
|
24
|
+
[ "${KJ_STANDALONE:-0}" = "1" ] && MODE="standalone"
|
|
16
25
|
|
|
17
|
-
die() {
|
|
18
|
-
echo "kj-install: $1" >&2
|
|
19
|
-
exit 1
|
|
20
|
-
}
|
|
26
|
+
die() { echo "kj-install: $1" >&2; exit 1; }
|
|
21
27
|
|
|
22
|
-
# --- Detect OS/arch
|
|
23
|
-
os="$(uname -s)"
|
|
24
|
-
arch="$(uname -m)"
|
|
28
|
+
# --- Detect OS/arch (shared by both routes). ---
|
|
29
|
+
os="$(uname -s)"; arch="$(uname -m)"
|
|
25
30
|
case "$os" in
|
|
26
31
|
Linux) os="linux" ;;
|
|
27
32
|
Darwin) os="darwin" ;;
|
|
28
|
-
*) die "unsupported OS '$os'. Supported: Linux, macOS.
|
|
33
|
+
*) die "unsupported OS '$os'. Supported: Linux, macOS." ;;
|
|
29
34
|
esac
|
|
30
35
|
case "$arch" in
|
|
31
36
|
x86_64 | amd64) arch="x64" ;;
|
|
@@ -33,80 +38,122 @@ case "$arch" in
|
|
|
33
38
|
*) die "unsupported architecture '$arch'" ;;
|
|
34
39
|
esac
|
|
35
40
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
# --- Downloader + checksum tool. ---
|
|
42
|
+
if command -v curl >/dev/null 2>&1; then fetch() { curl -fsSL "$1" -o "$2"; }
|
|
43
|
+
elif command -v wget >/dev/null 2>&1; then fetch() { wget -qO "$2" "$1"; }
|
|
44
|
+
else die "need curl or wget"; fi
|
|
45
|
+
if command -v sha256sum >/dev/null 2>&1; then sha256() { sha256sum "$1" | cut -d' ' -f1; }
|
|
46
|
+
elif command -v shasum >/dev/null 2>&1; then sha256() { shasum -a 256 "$1" | cut -d' ' -f1; }
|
|
47
|
+
else die "need sha256sum or shasum to verify downloads"; fi
|
|
42
48
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if [ "$VERSION" = "latest" ]; then
|
|
46
|
-
base="https://github.com/${REPO}/releases/latest/download"
|
|
47
|
-
else
|
|
48
|
-
base="https://github.com/${REPO}/releases/download/${VERSION}"
|
|
49
|
-
fi
|
|
49
|
+
tmp="$(mktemp -d "${TMPDIR:-/tmp}/kj-install.XXXXXX")"
|
|
50
|
+
trap 'rm -rf "$tmp"' EXIT INT TERM
|
|
50
51
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
52
|
+
path_hint() {
|
|
53
|
+
case ":${PATH}:" in
|
|
54
|
+
*":$1:"*) ;;
|
|
55
|
+
*) echo "kj-install: add '$1' to your PATH: export PATH=\"$1:\$PATH\" (persist it in ~/.bashrc / ~/.zshrc)" ;;
|
|
56
|
+
esac
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# --- Is there a usable Node (>= NODE_MAJOR.NODE_MIN_MINOR)? ---
|
|
60
|
+
node_ok() {
|
|
61
|
+
command -v node >/dev/null 2>&1 || return 1
|
|
62
|
+
v="$(node --version 2>/dev/null | sed 's/^v//')"
|
|
63
|
+
major="${v%%.*}"; rest="${v#*.}"; minor="${rest%%.*}"
|
|
64
|
+
[ "$major" -gt "$NODE_MAJOR" ] 2>/dev/null && return 0
|
|
65
|
+
[ "$major" -eq "$NODE_MAJOR" ] 2>/dev/null && [ "$minor" -ge "$NODE_MIN_MINOR" ] 2>/dev/null
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
npm_pkg() { if [ "$VERSION" = "latest" ]; then echo "karajan-code"; else echo "karajan-code@${VERSION#v}"; fi; }
|
|
69
|
+
|
|
70
|
+
if [ "$MODE" = "full" ]; then
|
|
71
|
+
if node_ok; then
|
|
72
|
+
echo "kj-install: Node $(node --version) found — installing via npm (full product)..."
|
|
73
|
+
npm install -g "$(npm_pkg)" || die "npm install failed. If it was a permissions error, set a user prefix (npm config set prefix ~/.local) and re-run."
|
|
74
|
+
echo "kj-install: installed $(kj --version 2>/dev/null || echo karajan-code). Run 'kj doctor' next."
|
|
75
|
+
exit 0
|
|
76
|
+
fi
|
|
59
77
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
78
|
+
echo "kj-install: no usable Node (need >= ${NODE_MAJOR}.${NODE_MIN_MINOR}) — provisioning official Node LTS into ~/.karajan/node (nothing system-wide)..."
|
|
79
|
+
dist="https://nodejs.org/dist/latest-v${NODE_MAJOR}.x"
|
|
80
|
+
fetch "${dist}/SHASUMS256.txt" "${tmp}/SHASUMS256.txt" || die "could not fetch the Node checksum list"
|
|
81
|
+
node_asset="$(grep -o "node-v[0-9.]*-${os}-${arch}\.tar\.gz" "${tmp}/SHASUMS256.txt" | head -1)"
|
|
82
|
+
[ -n "$node_asset" ] || die "no official Node build for ${os}-${arch}"
|
|
83
|
+
echo "kj-install: downloading ${node_asset}..."
|
|
84
|
+
fetch "${dist}/${node_asset}" "${tmp}/${node_asset}" || die "could not download Node"
|
|
85
|
+
expected="$(grep "${node_asset}\$" "${tmp}/SHASUMS256.txt" | head -1 | cut -d' ' -f1)"
|
|
86
|
+
actual="$(sha256 "${tmp}/${node_asset}")"
|
|
87
|
+
[ "$expected" = "$actual" ] || die "Node checksum mismatch — aborting, nothing installed"
|
|
88
|
+
|
|
89
|
+
# Stage the whole install (extract + npm install) in a sibling dir and only
|
|
90
|
+
# swap it into place once EVERYTHING succeeded — a failed download/extract/
|
|
91
|
+
# install must never destroy a previous working ~/.karajan/node.
|
|
92
|
+
node_home="$HOME/.karajan/node"
|
|
93
|
+
staging="${node_home}.staging.$$"
|
|
94
|
+
rm -rf "$staging"; mkdir -p "$staging"
|
|
95
|
+
trap 'rm -rf "$tmp" "$staging"' EXIT INT TERM
|
|
96
|
+
tar -xzf "${tmp}/${node_asset}" -C "$staging" --strip-components=1 || die "could not extract Node"
|
|
97
|
+
|
|
98
|
+
echo "kj-install: installing karajan-code with the provisioned Node..."
|
|
99
|
+
PATH="${staging}/bin:$PATH" "${staging}/bin/npm" install -g "$(npm_pkg)" || die "npm install failed with the provisioned Node"
|
|
100
|
+
|
|
101
|
+
# Swap keeping the previous install recoverable at EVERY instant: park it
|
|
102
|
+
# as a backup, arm a trap that restores it on any exit (signal included),
|
|
103
|
+
# move the staged one in, and only then disarm the trap and drop the
|
|
104
|
+
# backup — the user can never end up without a working install.
|
|
105
|
+
backup="${node_home}.old.$$"
|
|
106
|
+
if [ -e "$node_home" ]; then
|
|
107
|
+
mv "$node_home" "$backup" || die "could not park the previous install"
|
|
108
|
+
trap '[ -e "$node_home" ] || mv "$backup" "$node_home" 2>/dev/null; rm -rf "$tmp" "$staging"' EXIT INT TERM
|
|
109
|
+
fi
|
|
110
|
+
mv "$staging" "$node_home" || die "could not move the staged install into place (previous install restored)"
|
|
111
|
+
trap 'rm -rf "$tmp"' EXIT INT TERM
|
|
112
|
+
rm -rf "$backup"
|
|
113
|
+
mkdir -p "$INSTALL_DIR"
|
|
114
|
+
# Wrappers, not symlinks: the shebang (#!/usr/bin/env node) must find the
|
|
115
|
+
# provisioned Node even though it is not on the user's PATH.
|
|
116
|
+
for bin in kj karajan-mcp; do
|
|
117
|
+
[ -e "${node_home}/bin/${bin}" ] || continue
|
|
118
|
+
{
|
|
119
|
+
echo '#!/bin/sh'
|
|
120
|
+
echo "export PATH=\"${node_home}/bin:\$PATH\""
|
|
121
|
+
echo "exec \"${node_home}/bin/${bin}\" \"\$@\""
|
|
122
|
+
} >"${INSTALL_DIR}/${bin}"
|
|
123
|
+
chmod +x "${INSTALL_DIR}/${bin}"
|
|
124
|
+
done
|
|
125
|
+
installed="$("${INSTALL_DIR}/kj" --version 2>/dev/null || echo '?')"
|
|
126
|
+
echo "kj-install: installed kj ${installed} (full product) — kj at ${INSTALL_DIR}/kj"
|
|
127
|
+
path_hint "$INSTALL_DIR"
|
|
128
|
+
echo "kj-install: next — run 'kj doctor', then 'kj install-tools' to complete the whole stack."
|
|
129
|
+
exit 0
|
|
67
130
|
fi
|
|
68
131
|
|
|
69
|
-
# ---
|
|
70
|
-
|
|
71
|
-
|
|
132
|
+
# --- Standalone route: prebuilt single binary (CLI only). ---
|
|
133
|
+
echo "kj-install: standalone mode — single binary, NO native-module features (RAG, HU Board and MCP need the npm install)."
|
|
134
|
+
target="${os}-${arch}"
|
|
135
|
+
case "$target" in
|
|
136
|
+
linux-x64 | darwin-arm64) ;;
|
|
137
|
+
*) die "no prebuilt binary for '$target' (available: linux-x64, darwin-arm64) — run without --standalone for the npm route" ;;
|
|
138
|
+
esac
|
|
139
|
+
asset="kj-${target}"
|
|
140
|
+
if [ "$VERSION" = "latest" ]; then base="https://github.com/${REPO}/releases/latest/download"
|
|
141
|
+
else base="https://github.com/${REPO}/releases/download/${VERSION}"; fi
|
|
72
142
|
|
|
73
143
|
echo "kj-install: downloading ${asset} (${VERSION})..."
|
|
74
|
-
fetch "${base}/${asset}" "${tmp}/kj" || die "could not download ${base}/${asset}
|
|
144
|
+
fetch "${base}/${asset}" "${tmp}/kj" || die "could not download ${base}/${asset}"
|
|
75
145
|
fetch "${base}/${asset}.sha256" "${tmp}/kj.sha256" || die "could not download the checksum for ${asset}"
|
|
76
|
-
|
|
77
146
|
expected="$(cut -d' ' -f1 <"${tmp}/kj.sha256")"
|
|
78
147
|
actual="$(sha256 "${tmp}/kj")"
|
|
79
|
-
[ "$expected" = "$actual" ] || die "checksum mismatch
|
|
148
|
+
[ "$expected" = "$actual" ] || die "checksum mismatch — aborting, nothing installed"
|
|
80
149
|
|
|
81
|
-
# --- Install atomically: chmod on the temp file, then move into place. ---
|
|
82
150
|
chmod +x "${tmp}/kj"
|
|
83
151
|
mkdir -p "$INSTALL_DIR"
|
|
84
152
|
mv -f "${tmp}/kj" "${INSTALL_DIR}/kj"
|
|
85
|
-
|
|
86
|
-
# On macOS the binary is ad-hoc signed, not notarized with a paid Apple
|
|
87
|
-
# certificate, so Gatekeeper can quarantine it. Clear the flag on the copy
|
|
88
|
-
# we just checksummed ourselves (no-op on Linux / if the flag is absent).
|
|
89
153
|
if [ "$os" = "darwin" ] && command -v xattr >/dev/null 2>&1; then
|
|
90
154
|
xattr -d com.apple.quarantine "${INSTALL_DIR}/kj" 2>/dev/null || true
|
|
91
155
|
fi
|
|
92
|
-
|
|
93
156
|
installed="$("${INSTALL_DIR}/kj" --version 2>/dev/null || echo '?')"
|
|
94
|
-
echo "kj-install: installed kj ${installed} to ${INSTALL_DIR}/kj"
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
case ":${PATH}:" in
|
|
98
|
-
*":${INSTALL_DIR}:"*) echo "kj-install: '${INSTALL_DIR}' is on your PATH — run 'kj --help' to get started." ;;
|
|
99
|
-
*)
|
|
100
|
-
echo "kj-install: '${INSTALL_DIR}' is not on your PATH. Add it with:"
|
|
101
|
-
echo " export PATH=\"${INSTALL_DIR}:\$PATH\""
|
|
102
|
-
echo " (add that line to ~/.bashrc, ~/.zshrc or ~/.profile to make it permanent)"
|
|
103
|
-
;;
|
|
104
|
-
esac
|
|
105
|
-
|
|
106
|
-
# --- Prerequisites: the binary bundles no toolchain. kj orchestrates ---
|
|
107
|
-
# --- external tools, so name the hard requirements and let `kj doctor` ---
|
|
108
|
-
# --- check them precisely for this machine.
|
|
109
|
-
echo ""
|
|
110
|
-
echo "kj-install: next step — run 'kj doctor' to check prerequisites."
|
|
111
|
-
echo " Required: git, plus at least one agent CLI (Claude Code, Codex or Gemini)."
|
|
112
|
-
echo " Optional: Docker (local models, SonarQube) and Node/npm (helper tools: Squeezr, qmd)."
|
|
157
|
+
echo "kj-install: installed standalone kj ${installed} to ${INSTALL_DIR}/kj"
|
|
158
|
+
path_hint "$INSTALL_DIR"
|
|
159
|
+
echo "kj-install: next — run 'kj doctor'. For the full product later: re-run this installer without --standalone."
|
|
@@ -28,7 +28,17 @@ const execFileAsync = promisify(execFile);
|
|
|
28
28
|
|
|
29
29
|
// git and the agent CLI lead the list: both are `kj doctor` *required* tools,
|
|
30
30
|
// so a blank machine wants them before the optional audit tools.
|
|
31
|
-
|
|
31
|
+
// KJC-TSK-0657: rtk/squeezr/qmd (token+context optimizers, previously
|
|
32
|
+
// init-only) complete the list — one pass leaves the machine 100%
|
|
33
|
+
// operational, per the quality-default-on product rule.
|
|
34
|
+
const ALL_TOOLS = ["git", "agent-cli", "semgrep", "osv-scanner", "lighthouse", "docker", "sonar", "rtk", "squeezr", "qmd"];
|
|
35
|
+
|
|
36
|
+
// Optimizer tools reuse the init installers (same {ok, version, error} contract).
|
|
37
|
+
const OPTIMIZER_INSTALLERS = {
|
|
38
|
+
rtk: () => import("../utils/rtk-install.js").then((m) => m.installRtk),
|
|
39
|
+
squeezr: () => import("../utils/squeezr-install.js").then((m) => m.installSqueezr),
|
|
40
|
+
qmd: () => import("../utils/qmd-install.js").then((m) => m.installQmd),
|
|
41
|
+
};
|
|
32
42
|
|
|
33
43
|
// The default pipeline is coder=claude, reviewer=codex, so `agent-cli` installs
|
|
34
44
|
// exactly those two. gemini stays out of the default: it is a supported
|
|
@@ -335,6 +345,22 @@ export async function installToolsCommand(opts = {}) {
|
|
|
335
345
|
continue;
|
|
336
346
|
}
|
|
337
347
|
|
|
348
|
+
if (OPTIMIZER_INSTALLERS[tool]) {
|
|
349
|
+
if (dryRun) {
|
|
350
|
+
results.push({ tool, action: "planned", command: `kj install-tools --only ${tool}` });
|
|
351
|
+
logger.info?.(`▸ ${tool}: would install (init installer)`);
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
const proceed = yes || await promptYesNo(`Install ${tool}?`);
|
|
355
|
+
if (!proceed) { results.push({ tool, action: "declined" }); continue; }
|
|
356
|
+
const install = await OPTIMIZER_INSTALLERS[tool]();
|
|
357
|
+
const r = await install(logger);
|
|
358
|
+
results.push(r.ok
|
|
359
|
+
? { tool, action: "installed", version: r.version }
|
|
360
|
+
: { tool, action: "failed", error: r.error });
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
|
|
338
364
|
if (tool === "git") {
|
|
339
365
|
const result = await handleGit({ available, dryRun, yes, logger });
|
|
340
366
|
results.push(result);
|