@bitmilldev/warden 1.0.1 → 1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bitmilldev/warden",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "AI Coding Session Guardian — runtime intelligence layer for Claude Code, Gemini CLI, and more",
5
5
  "keywords": [
6
6
  "ai",
@@ -2,7 +2,7 @@
2
2
  // Warden npm/bun postinstall — downloads the platform-specific binary
3
3
  // to ~/.warden/bin/ and registers PATH.
4
4
 
5
- const { execSync, spawnSync } = require("child_process");
5
+ const { spawnSync } = require("child_process");
6
6
  const fs = require("fs");
7
7
  const path = require("path");
8
8
  const os = require("os");
@@ -32,78 +32,156 @@ async function main() {
32
32
 
33
33
  const wardenHome = path.join(os.homedir(), ".warden");
34
34
  const binDir = path.join(wardenHome, "bin");
35
- const destName = os.platform() === "win32" ? "warden.exe" : "warden";
36
- const dest = path.join(binDir, destName);
35
+ const ext = os.platform() === "win32" ? ".exe" : "";
36
+ const dest = path.join(binDir, `warden${ext}`);
37
37
 
38
38
  // Create directories
39
39
  fs.mkdirSync(binDir, { recursive: true });
40
40
  fs.mkdirSync(path.join(wardenHome, "rules"), { recursive: true });
41
41
  fs.mkdirSync(path.join(wardenHome, "projects"), { recursive: true });
42
42
 
43
- // Download binary from GitHub Releases
43
+ // Strategy 1: Check if warden is already installed via cargo and is current version
44
+ const localCargo = findCargoBinary();
45
+ if (localCargo) {
46
+ console.log(`Found cargo-installed warden at ${localCargo}`);
47
+ if (localCargo !== dest) {
48
+ fs.copyFileSync(localCargo, dest);
49
+ console.log(`Copied to ${dest}`);
50
+ }
51
+ copyRelay(binDir, localCargo);
52
+ postInstall(dest);
53
+ return;
54
+ }
55
+
56
+ // Strategy 2: Download from GitHub Releases
44
57
  const url = `https://github.com/${REPO}/releases/download/v${VERSION}/${binary}`;
45
58
  console.log(`Downloading warden v${VERSION} for ${platform}...`);
46
59
 
47
60
  try {
48
61
  await download(url, dest);
62
+ const stat = fs.statSync(dest);
63
+ if (stat.size === 0) {
64
+ fs.unlinkSync(dest);
65
+ throw new Error("Downloaded file is empty (asset may not exist in release)");
66
+ }
67
+
49
68
  if (os.platform() === "win32") {
50
- // Remove Zone.Identifier to prevent SmartScreen "Access denied"
51
69
  try { fs.unlinkSync(dest + ":Zone.Identifier"); } catch (e) {}
52
70
  } else {
53
71
  fs.chmodSync(dest, 0o755);
54
72
  }
55
73
 
56
- // Also download the relay binary (Windows only — prevents CMD flicker)
74
+ // Download relay binary (Windows only — prevents CMD flicker)
57
75
  if (os.platform() === "win32") {
58
76
  const relayBinary = binary.replace("warden-", "warden-relay-");
59
77
  const relayDest = path.join(binDir, "warden-relay.exe");
60
78
  const relayUrl = `https://github.com/${REPO}/releases/download/v${VERSION}/${relayBinary}`;
61
79
  try {
62
80
  await download(relayUrl, relayDest);
63
- try { fs.unlinkSync(relayDest + ":Zone.Identifier"); } catch (e) {}
64
- console.log(`Installed relay to ${relayDest}`);
81
+ const relayStat = fs.statSync(relayDest);
82
+ if (relayStat.size === 0) {
83
+ fs.unlinkSync(relayDest);
84
+ } else {
85
+ try { fs.unlinkSync(relayDest + ":Zone.Identifier"); } catch (e) {}
86
+ console.log(`Installed relay to ${relayDest}`);
87
+ }
65
88
  } catch (e) {
66
89
  // Relay is optional — warden works without it (just has CMD flicker)
67
90
  }
68
91
  }
69
92
 
70
- console.log(`Installed to ${dest}`);
93
+ postInstall(dest);
94
+ } catch (err) {
95
+ // Clean up empty/partial file
96
+ try { if (fs.existsSync(dest) && fs.statSync(dest).size === 0) fs.unlinkSync(dest); } catch (e) {}
97
+
98
+ console.error(`Download failed: ${err.message}`);
99
+ console.error("");
100
+ console.error("Alternative install methods:");
101
+ console.error(" cargo install warden-ai");
102
+ console.error(" Download from: https://github.com/ekud12/warden/releases");
103
+ process.exit(0); // Don't fail npm install
104
+ }
105
+ }
106
+
107
+ function postInstall(dest) {
108
+ console.log(`Installed to ${dest}`);
109
+
110
+ // Verify binary works
111
+ try {
112
+ spawnSync(dest, ["version"], { stdio: "inherit", timeout: 5000 });
113
+ } catch (e) {
114
+ // Ignore — binary might need different setup
115
+ }
116
+
117
+ console.log("");
118
+ console.log("Run 'warden init' to complete setup (install CLI tools, configure hooks).");
119
+ console.log("Or: 'warden install claude-code' / 'warden install gemini-cli'");
120
+ }
121
+
122
+ function findCargoBinary() {
123
+ // Check common cargo install locations
124
+ const ext = os.platform() === "win32" ? ".exe" : "";
125
+ const candidates = [
126
+ path.join(os.homedir(), ".cargo", "bin", `warden${ext}`),
127
+ ];
128
+
129
+ // Also check if there's a local build in the project (for development)
130
+ const envBinary = process.env.WARDEN_BINARY;
131
+ if (envBinary && fs.existsSync(envBinary)) {
132
+ return envBinary;
133
+ }
134
+
135
+ for (const candidate of candidates) {
136
+ if (fs.existsSync(candidate)) {
137
+ const stat = fs.statSync(candidate);
138
+ if (stat.size > 0) return candidate;
139
+ }
140
+ }
141
+ return null;
142
+ }
143
+
144
+ function copyRelay(binDir, sourceBinaryDir) {
145
+ if (os.platform() !== "win32") return;
146
+
147
+ const sourceDir = path.dirname(sourceBinaryDir);
148
+ const relaySource = path.join(sourceDir, "warden-relay.exe");
149
+ const relayDest = path.join(binDir, "warden-relay.exe");
71
150
 
72
- // Run warden init (non-interactive: just PATH + config)
151
+ if (fs.existsSync(relaySource) && relaySource !== relayDest) {
73
152
  try {
74
- spawnSync(dest, ["version"], { stdio: "inherit" });
153
+ fs.copyFileSync(relaySource, relayDest);
154
+ console.log(`Copied relay to ${relayDest}`);
75
155
  } catch (e) {
76
- // Ignore — binary might need different setup
156
+ // Non-fatal
77
157
  }
78
-
79
- console.log("");
80
- console.log("Run 'warden init' to complete setup (install CLI tools, configure hooks).");
81
- console.log(`Or: 'warden install claude-code' / 'warden install gemini-cli'`);
82
- } catch (err) {
83
- console.error(`Download failed: ${err.message}`);
84
- console.error("Install from source: cargo install warden-ai");
85
- process.exit(0); // Don't fail npm install
86
158
  }
87
159
  }
88
160
 
89
- function download(url, dest) {
161
+ function download(url, dest, redirects = 0) {
162
+ if (redirects > 10) return Promise.reject(new Error("Too many redirects"));
90
163
  return new Promise((resolve, reject) => {
91
- const file = fs.createWriteStream(dest);
92
- const request = (urlStr) => {
93
- https.get(urlStr, (response) => {
94
- if (response.statusCode === 302 || response.statusCode === 301) {
95
- request(response.headers.location);
96
- return;
97
- }
98
- if (response.statusCode !== 200) {
99
- reject(new Error(`HTTP ${response.statusCode}`));
100
- return;
101
- }
102
- response.pipe(file);
103
- file.on("finish", () => { file.close(); resolve(); });
104
- }).on("error", reject);
105
- };
106
- request(url);
164
+ const proto = url.startsWith("https") ? https : require("http");
165
+ proto.get(url, (response) => {
166
+ if (response.statusCode === 301 || response.statusCode === 302) {
167
+ response.resume();
168
+ return resolve(download(response.headers.location, dest, redirects + 1));
169
+ }
170
+ if (response.statusCode !== 200) {
171
+ response.resume();
172
+ return reject(new Error(`HTTP ${response.statusCode} for ${url}`));
173
+ }
174
+ const file = fs.createWriteStream(dest);
175
+ response.pipe(file);
176
+ file.on("finish", () => { file.close(); resolve(); });
177
+ file.on("error", (e) => {
178
+ try { fs.unlinkSync(dest); } catch (ignore) {}
179
+ reject(e);
180
+ });
181
+ }).on("error", (e) => {
182
+ try { fs.unlinkSync(dest); } catch (ignore) {}
183
+ reject(e);
184
+ });
107
185
  });
108
186
  }
109
187