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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "karajan-code",
3
- "version": "4.1.1",
3
+ "version": "4.1.2",
4
4
  "description": "Local multi-agent coding orchestrator with TDD, SonarQube, and code review pipeline",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0",
@@ -1,31 +1,36 @@
1
1
  #!/bin/sh
2
- # Karajan Code — standalone binary installer (no Node required).
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
- # 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.
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 — no bashisms, no Node. KJC-TSK-0593.
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, map to the release asset names we actually build. ---
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. Use npm instead: npm install -g karajan-code" ;;
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
- target="${os}-${arch}"
37
- case "$target" in
38
- linux-x64) ;;
39
- darwin-arm64) ;;
40
- *) die "no prebuilt binary for '$target'. Available: linux-x64, darwin-arm64. Use npm instead: npm install -g karajan-code" ;;
41
- esac
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
- # --- Resolve the download URL for the requested version (or latest). ---
44
- asset="kj-${target}"
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
- # --- Pick a downloader. ---
52
- if command -v curl >/dev/null 2>&1; then
53
- fetch() { curl -fsSL "$1" -o "$2"; }
54
- elif command -v wget >/dev/null 2>&1; then
55
- fetch() { wget -qO "$2" "$1"; }
56
- else
57
- die "need curl or wget to download the binary"
58
- fi
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
- # --- Pick a checksum tool. ---
61
- if command -v sha256sum >/dev/null 2>&1; then
62
- sha256() { sha256sum "$1" | cut -d' ' -f1; }
63
- elif command -v shasum >/dev/null 2>&1; then
64
- sha256() { shasum -a 256 "$1" | cut -d' ' -f1; }
65
- else
66
- die "need sha256sum or shasum to verify the download"
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
- # --- Download to a temp dir; only install after the checksum matches. ---
70
- tmp="$(mktemp -d "${TMPDIR:-/tmp}/kj-install.XXXXXX")"
71
- trap 'rm -rf "$tmp"' EXIT INT TERM
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} — does that version/asset exist?"
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 (expected ${expected}, got ${actual}). Aborting, nothing installed."
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
- # --- Tell the user how to reach it if the dir is not on PATH. ---
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
- const ALL_TOOLS = ["git", "agent-cli", "semgrep", "osv-scanner", "lighthouse", "docker", "sonar"];
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);