juggernaut-bedrock 4.0.3 → 4.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.
Files changed (3) hide show
  1. package/README.md +44 -7
  2. package/install.js +122 -15
  3. package/package.json +10 -7
package/README.md CHANGED
@@ -50,7 +50,7 @@ juggernaut apply --auth=iam
50
50
  That one command:
51
51
 
52
52
  1. **Writes** Bedrock config to `~/.claude/settings.json` (or project scope)
53
- 2. **Sets** model IDs, region, effort level, and `CLAUDE_CODE_USE_BEDROCK=1` — only after credentials check out
53
+ 2. **Sets** model IDs, region, effort level, permission mode, and `CLAUDE_CODE_USE_BEDROCK=1` — only after credentials check out
54
54
  3. **Installs** a `claude` launcher shim that reads your token from the keychain and execs the real binary
55
55
 
56
56
  No `.bashrc` edits. No copying API keys into env vars. No "why isn't Bedrock routing?" at 2am.
@@ -87,14 +87,51 @@ No `.bashrc` edits. No copying API keys into env vars. No "why isn't Bedrock rou
87
87
 
88
88
  ## Default models
89
89
 
90
- | Tier | Model |
91
- |------|-------|
92
- | **Primary** | Claude Sonnet 4.6 |
93
- | **Opus** | Claude Opus 4.7 |
94
- | **Fast** | Claude Haiku 4.5 |
90
+ | Tier | Model | Global inference profile |
91
+ |------|-------|--------------------------|
92
+ | **Primary** | Claude Sonnet 4.6 | `global.anthropic.claude-sonnet-4-6` |
93
+ | **Opus** | Claude Opus 4.8 | `global.anthropic.claude-opus-4-8` |
94
+ | **Fast** | Claude Haiku 4.5 | `global.anthropic.claude-haiku-4-5-20251001-v1:0` |
95
95
 
96
96
  Override any tier: `juggernaut apply --auth=iam --model=global.anthropic.claude-sonnet-4-6`
97
97
 
98
+ ## Effort levels
99
+
100
+ Controls adaptive thinking depth. Valid values: `low | medium | high | xhigh | max`
101
+
102
+ ```bash
103
+ juggernaut apply --auth=iam --effort=max
104
+ ```
105
+
106
+ Default: `xhigh`. On Opus 4.8/4.7, effort level controls adaptive thinking depth — manual thinking mode is not supported.
107
+
108
+ ## Permission modes
109
+
110
+ Controls how Claude Code handles tool-use approvals:
111
+
112
+ | Mode | Behavior |
113
+ |------|----------|
114
+ | `default` | Prompts for each action |
115
+ | `acceptEdits` | Auto-approves file edits |
116
+ | `plan` | Propose only, no execution |
117
+ | `auto` | Agentic safety classifier |
118
+ | `bypassPermissions` | Skip all prompts (containers/VMs only) |
119
+
120
+ ```bash
121
+ juggernaut apply --auth=iam --mode=auto
122
+ ```
123
+
124
+ Auto mode on Bedrock requires `CLAUDE_CODE_ENABLE_AUTO_MODE=1` — Juggernaut sets this automatically.
125
+
126
+ ## Other options
127
+
128
+ ```bash
129
+ --always-thinking # enable extended thinking by default (alwaysThinkingEnabled)
130
+ --service-tier=flex # Bedrock service tier: default | flex | priority
131
+ --opusplan # route /plan to Opus 4.8, execution to Sonnet 4.6
132
+ --scope=project # write to ./.claude/settings.json instead of ~/.claude/
133
+ ```
134
+
98
135
  ## Troubleshooting
99
136
 
100
137
  Stuck? Start here:
@@ -115,4 +152,4 @@ Full docs, IAM policy, migration guide, and platform notes:
115
152
 
116
153
  MIT — see [LICENSE](https://github.com/jpvelasco/juggernaut/blob/main/LICENSE).
117
154
 
118
- Juggernaut is an independent tool, not affiliated with Anthropic or Amazon Web Services.
155
+ Juggernaut is an independent tool, not affiliated with Anthropic or Amazon Web Services.
package/install.js CHANGED
@@ -3,17 +3,55 @@
3
3
  const https = require("https");
4
4
  const fs = require("fs");
5
5
  const path = require("path");
6
- const { Readable } = require("stream");
6
+ const os = require("os");
7
7
  const crypto = require("crypto");
8
+ const { execFile, execFileSync } = require("child_process");
9
+ const { promisify } = require("util");
10
+
11
+ const execFileAsync = promisify(execFile);
8
12
 
9
13
  const REPO = "jpvelasco/juggernaut";
10
- const BIN_DIR = path.join(__dirname, "bin");
14
+ const PACKAGE_DIR = __dirname;
15
+ const BIN_DIR = path.join(PACKAGE_DIR, "bin");
16
+ const TMP_PREFIX = path.join(os.tmpdir(), "juggernaut-install-");
17
+ const ARCHIVE_TAR = "archive.tar.gz";
18
+ const ARCHIVE_ZIP = "archive.zip";
19
+ const BIN_NAME = "juggernaut";
11
20
  const ALLOWED_HOSTS = new Set([
12
21
  "github.com",
13
22
  "api.github.com",
14
23
  "release-assets.githubusercontent.com"
15
24
  ]);
16
25
 
26
+ function joinUnder(base, ...segments) {
27
+ if (base.indexOf("\0") !== -1) {
28
+ throw new Error("Invalid base path");
29
+ }
30
+ for (const seg of segments) {
31
+ if (seg.indexOf("..") !== -1 || seg.indexOf("/") !== -1 || seg.indexOf("\\") !== -1) {
32
+ throw new Error(`Invalid path segment: ${seg}`);
33
+ }
34
+ }
35
+ const joined = path.join(base, ...segments);
36
+ const normalBase = path.normalize(base) + path.sep;
37
+ const normalJoined = path.normalize(joined);
38
+ const compare = process.platform === "win32"
39
+ ? (a, b) => a.toLowerCase().startsWith(b.toLowerCase())
40
+ : (a, b) => a.startsWith(b);
41
+ if (!compare(normalJoined, normalBase)) {
42
+ const exactBase = process.platform === "win32"
43
+ ? path.normalize(base).toLowerCase()
44
+ : path.normalize(base);
45
+ const exactJoined = process.platform === "win32"
46
+ ? normalJoined.toLowerCase()
47
+ : normalJoined;
48
+ if (exactJoined !== exactBase) {
49
+ throw new Error(`Path traversal detected: ${joined} is outside ${base}`);
50
+ }
51
+ }
52
+ return normalJoined;
53
+ }
54
+
17
55
  function getPlatform() {
18
56
  const osMap = { darwin: "darwin", linux: "linux", win32: "windows" };
19
57
  const archMap = { x64: "amd64", arm64: "arm64" };
@@ -58,18 +96,93 @@ async function getLatestVersion() {
58
96
  return release.tag_name.replace(/^v/, "");
59
97
  }
60
98
 
99
+ function pickArchive(platform, checksumsText) {
100
+ const tarArchive = `juggernaut_${platform}.tar.gz`;
101
+ if (checksumsText.includes(tarArchive)) return { name: tarArchive, kind: "tar.gz" };
102
+ const zipArchive = `juggernaut_${platform}.zip`;
103
+ if (process.platform === "win32" && checksumsText.includes(zipArchive)) {
104
+ return { name: zipArchive, kind: "zip" };
105
+ }
106
+ throw new Error(`No supported archive found in release checksums for ${platform}`);
107
+ }
108
+
109
+ function extractTarGz(archiveBuf) {
110
+ const tmpDir = fs.mkdtempSync(TMP_PREFIX);
111
+ const archivePath = joinUnder(tmpDir, ARCHIVE_TAR);
112
+ const prevCwd = process.cwd();
113
+ try {
114
+ process.chdir(tmpDir);
115
+ fs.writeFileSync(ARCHIVE_TAR, archiveBuf);
116
+ process.chdir(PACKAGE_DIR);
117
+ fs.mkdirSync("bin", { recursive: true });
118
+ try {
119
+ execFileSync("tar", ["-xzf", archivePath, "-C", BIN_DIR], { stdio: "pipe" });
120
+ } catch (err) {
121
+ if (err.code === "ENOENT") {
122
+ throw new Error(
123
+ "tar not found on PATH. On Windows, use Windows 10 version 1803 or later (includes tar.exe)."
124
+ );
125
+ }
126
+ const detail = err.stderr ? err.stderr.toString().trim() : err.message;
127
+ throw new Error(`tar extraction failed: ${detail}`);
128
+ }
129
+ if (process.platform !== "win32") {
130
+ process.chdir(BIN_DIR);
131
+ if (fs.existsSync(BIN_NAME)) {
132
+ fs.chmodSync(BIN_NAME, 0o700);
133
+ }
134
+ }
135
+ } finally {
136
+ process.chdir(prevCwd);
137
+ fs.rmSync(tmpDir, { recursive: true, force: true });
138
+ }
139
+ }
140
+
141
+ async function extractZip(archiveBuf) {
142
+ const tmpDir = fs.mkdtempSync(TMP_PREFIX);
143
+ const archivePath = joinUnder(tmpDir, ARCHIVE_ZIP);
144
+ const prevCwd = process.cwd();
145
+ try {
146
+ process.chdir(tmpDir);
147
+ fs.writeFileSync(ARCHIVE_ZIP, archiveBuf);
148
+ process.chdir(PACKAGE_DIR);
149
+ fs.mkdirSync("bin", { recursive: true });
150
+ const script = [
151
+ "$ErrorActionPreference = 'Stop'",
152
+ `Expand-Archive -LiteralPath '${archivePath.replace(/'/g, "''")}' -DestinationPath '${BIN_DIR.replace(/'/g, "''")}' -Force`
153
+ ].join("; ");
154
+ try {
155
+ await execFileAsync(
156
+ "powershell",
157
+ ["-NoProfile", "-NonInteractive", "-Command", script],
158
+ { stdio: "pipe" }
159
+ );
160
+ } catch (err) {
161
+ if (err.code === "ENOENT") {
162
+ throw new Error(
163
+ "PowerShell not found. PowerShell 5.0+ is required to extract archives on Windows."
164
+ );
165
+ }
166
+ const detail = err.stderr ? err.stderr.toString().trim() : err.message;
167
+ throw new Error(`ZIP extraction failed: ${detail}`);
168
+ }
169
+ } finally {
170
+ process.chdir(prevCwd);
171
+ fs.rmSync(tmpDir, { recursive: true, force: true });
172
+ }
173
+ }
174
+
61
175
  async function main() {
62
- const { platform, os: osName } = getPlatform();
176
+ const { platform } = getPlatform();
63
177
  const version = await getLatestVersion();
64
- const ext = osName === "windows" ? "zip" : "tar.gz";
65
- const archive = `juggernaut_${platform}.${ext}`;
66
178
  const baseUrl = `https://github.com/${REPO}/releases/download/v${version}`;
179
+ const checksumsBuf = await httpsGetBuffer(`${baseUrl}/checksums.txt`);
180
+ const checksums = checksumsBuf.toString("utf8");
181
+ const { name: archive, kind } = pickArchive(platform, checksums);
67
182
 
68
183
  console.log(`Downloading Juggernaut v${version} (${platform})...`);
69
184
  const archiveBuf = await httpsGetBuffer(`${baseUrl}/${archive}`);
70
- const checksumsBuf = await httpsGetBuffer(`${baseUrl}/checksums.txt`);
71
185
 
72
- const checksums = checksumsBuf.toString("utf8");
73
186
  const line = checksums.split("\n").find((l) => l.includes(archive));
74
187
  if (!line) throw new Error(`Checksum not found for ${archive}`);
75
188
  const expected = line.trim().split(/\s+/)[0].toLowerCase();
@@ -78,14 +191,8 @@ async function main() {
78
191
  throw new Error(`Checksum mismatch for ${archive}\n expected: ${expected}\n got: ${actual}`);
79
192
  }
80
193
 
81
- if (ext === "zip") {
82
- const AdmZip = require("adm-zip");
83
- const zip = new AdmZip(archiveBuf);
84
- zip.extractEntryTo("juggernaut.exe", BIN_DIR, false, true);
85
- } else {
86
- const tar = require("tar");
87
- await tar.x({ cwd: BIN_DIR, mode: 0o700, strict: true }, Readable.from(archiveBuf));
88
- }
194
+ if (kind === "zip") await extractZip(archiveBuf);
195
+ else extractTarGz(archiveBuf);
89
196
 
90
197
  console.log(`Juggernaut v${version} installed successfully.`);
91
198
  console.log(`Run: juggernaut apply`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juggernaut-bedrock",
3
- "version": "4.0.3",
3
+ "version": "4.1.0",
4
4
  "description": "Route Claude Code through Amazon Bedrock in one command — IAM, SSO, or API key. Cross-platform CLI for GenAI developers.",
5
5
  "bin": {
6
6
  "juggernaut": "./bin/juggernaut"
@@ -8,12 +8,15 @@
8
8
  "scripts": {
9
9
  "postinstall": "node install.js"
10
10
  },
11
- "dependencies": {
12
- "tar": "7.5.16",
13
- "adm-zip": "0.5.17"
14
- },
15
- "os": ["darwin", "linux", "win32"],
16
- "cpu": ["x64", "arm64"],
11
+ "os": [
12
+ "darwin",
13
+ "linux",
14
+ "win32"
15
+ ],
16
+ "cpu": [
17
+ "x64",
18
+ "arm64"
19
+ ],
17
20
  "keywords": [
18
21
  "claude",
19
22
  "claude-code",