watchmen-cli 1.0.11 → 1.0.13

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 (4) hide show
  1. package/bin/wm.js +28 -0
  2. package/install.js +124 -24
  3. package/package.json +3 -3
  4. package/bin/wm +0 -5
package/bin/wm.js ADDED
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const { execFileSync } = require("child_process");
5
+ const path = require("path");
6
+ const fs = require("fs");
7
+
8
+ const binDir = __dirname;
9
+ const ext = process.platform === "win32" ? ".exe" : "";
10
+ const binPath = path.join(binDir, `wm${ext}`);
11
+
12
+ if (!fs.existsSync(binPath)) {
13
+ console.error(
14
+ "wm binary not found. Run: npm rebuild watchmen-cli\n" +
15
+ `Expected at: ${binPath}`
16
+ );
17
+ process.exit(1);
18
+ }
19
+
20
+ try {
21
+ execFileSync(binPath, process.argv.slice(2), {
22
+ stdio: "inherit",
23
+ env: process.env,
24
+ });
25
+ } catch (err) {
26
+ // execFileSync throws on non-zero exit — forward the exit code
27
+ process.exit(err.status || 1);
28
+ }
package/install.js CHANGED
@@ -5,17 +5,21 @@ const https = require("https");
5
5
  const http = require("http");
6
6
  const fs = require("fs");
7
7
  const path = require("path");
8
- const { createHash } = require("crypto");
9
8
  const { execSync } = require("child_process");
10
9
 
11
10
  const VERSION = require("./package.json").version;
12
11
  const DOWNLOAD_BASE = "https://releases.trywatchmen.cloud/download/community";
13
12
 
13
+ // 60s connect timeout, 120s total — covers slow connections without hanging forever
14
+ const CONNECT_TIMEOUT_MS = 60_000;
15
+ const TOTAL_TIMEOUT_MS = 120_000;
16
+
14
17
  // Platform → download path mapping
15
18
  const PLATFORMS = {
16
19
  "darwin-arm64": "macos-arm64",
17
20
  "darwin-x64": "macos-arm64", // Rosetta 2 fallback
18
21
  "linux-x64": "linux-x86_64",
22
+ "linux-arm64": "linux-x86_64", // best-effort — no native arm64 yet
19
23
  "win32-x64": "windows-x86_64",
20
24
  };
21
25
 
@@ -28,69 +32,165 @@ function getPlatformSlug() {
28
32
  const slug = PLATFORMS[key];
29
33
  if (!slug) {
30
34
  console.error(
31
- `Unsupported platform: ${key}\nSupported: ${Object.keys(PLATFORMS).join(", ")}`
35
+ `\n Unsupported platform: ${key}\n` +
36
+ ` Supported: ${Object.keys(PLATFORMS).join(", ")}\n\n` +
37
+ ` You can download manually from: https://trywatchmen.cloud/docs/getting-started/installation\n`
32
38
  );
33
39
  process.exit(1);
34
40
  }
35
41
  return slug;
36
42
  }
37
43
 
38
- function download(url) {
44
+ function formatBytes(bytes) {
45
+ if (bytes < 1024) return `${bytes} B`;
46
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} KB`;
47
+ return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
48
+ }
49
+
50
+ function download(url, maxRedirects = 5) {
39
51
  return new Promise((resolve, reject) => {
52
+ if (maxRedirects <= 0) {
53
+ return reject(new Error("Too many redirects"));
54
+ }
55
+
40
56
  const client = url.startsWith("https") ? https : http;
41
- client
42
- .get(url, { headers: { "User-Agent": "watchmen-cli-npm" } }, (res) => {
43
- // Follow redirects (GitHub releases redirect to S3)
57
+ const req = client.get(
58
+ url,
59
+ {
60
+ headers: { "User-Agent": `watchmen-cli-npm/${VERSION}` },
61
+ timeout: CONNECT_TIMEOUT_MS,
62
+ },
63
+ (res) => {
64
+ // Follow redirects
44
65
  if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
45
- return download(res.headers.location).then(resolve).catch(reject);
66
+ res.resume(); // drain response
67
+ return download(res.headers.location, maxRedirects - 1).then(resolve).catch(reject);
46
68
  }
47
69
  if (res.statusCode !== 200) {
48
- return reject(new Error(`HTTP ${res.statusCode} for ${url}`));
70
+ res.resume();
71
+ return reject(new Error(`HTTP ${res.statusCode} from ${url.split("?")[0]}`));
49
72
  }
73
+
74
+ const contentLength = parseInt(res.headers["content-length"], 10) || 0;
50
75
  const chunks = [];
51
- res.on("data", (chunk) => chunks.push(chunk));
52
- res.on("end", () => resolve(Buffer.concat(chunks)));
53
- res.on("error", reject);
54
- })
55
- .on("error", reject);
76
+ let received = 0;
77
+ const startTime = Date.now();
78
+
79
+ // Progress reporting
80
+ const progressInterval = setInterval(() => {
81
+ const elapsed = ((Date.now() - startTime) / 1000).toFixed(0);
82
+ if (contentLength > 0) {
83
+ const pct = ((received / contentLength) * 100).toFixed(0);
84
+ process.stdout.write(`\r Downloading... ${formatBytes(received)} / ${formatBytes(contentLength)} (${pct}%) [${elapsed}s]`);
85
+ } else {
86
+ process.stdout.write(`\r Downloading... ${formatBytes(received)} [${elapsed}s]`);
87
+ }
88
+ }, 500);
89
+
90
+ res.on("data", (chunk) => {
91
+ chunks.push(chunk);
92
+ received += chunk.length;
93
+ });
94
+
95
+ res.on("end", () => {
96
+ clearInterval(progressInterval);
97
+ const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
98
+ process.stdout.write(`\r Downloaded ${formatBytes(received)} in ${elapsed}s${" ".repeat(20)}\n`);
99
+ resolve(Buffer.concat(chunks));
100
+ });
101
+
102
+ res.on("error", (err) => {
103
+ clearInterval(progressInterval);
104
+ reject(err);
105
+ });
106
+ }
107
+ );
108
+
109
+ req.on("timeout", () => {
110
+ req.destroy();
111
+ reject(new Error(`Connection timed out after ${CONNECT_TIMEOUT_MS / 1000}s`));
112
+ });
113
+
114
+ req.on("error", reject);
115
+
116
+ // Hard total timeout
117
+ setTimeout(() => {
118
+ req.destroy();
119
+ reject(new Error(`Download timed out after ${TOTAL_TIMEOUT_MS / 1000}s — try again or download manually from https://trywatchmen.cloud`));
120
+ }, TOTAL_TIMEOUT_MS);
56
121
  });
57
122
  }
58
123
 
59
124
  async function main() {
60
125
  const platformSlug = getPlatformSlug();
126
+ const platformKey = getPlatformKey();
127
+ const isWindows = process.platform === "win32";
61
128
  const binDir = path.join(__dirname, "bin");
62
- const binName = process.platform === "win32" ? "wm.exe" : "wm";
129
+ const binName = isWindows ? "wm.exe" : "wm";
63
130
  const binPath = path.join(binDir, binName);
64
131
 
65
132
  // Skip if already installed at correct version
66
133
  if (fs.existsSync(binPath)) {
67
134
  try {
68
- const installed = execSync(`"${binPath}" version`, { encoding: "utf8" });
135
+ const installed = execSync(`"${binPath}" version`, {
136
+ encoding: "utf8",
137
+ timeout: 10_000,
138
+ });
69
139
  if (installed.includes(VERSION)) {
70
- console.log(`wm v${VERSION} already installed`);
140
+ console.log(` wm v${VERSION} already installed at ${binPath}`);
71
141
  return;
72
142
  }
73
- } catch {}
143
+ console.log(` Upgrading wm to v${VERSION}...`);
144
+ } catch {
145
+ // Binary exists but broken/wrong version — redownload
146
+ }
74
147
  }
75
148
 
76
- // Pre-signed URL API redirects to S3
77
149
  const url = `${DOWNLOAD_BASE}/${platformSlug}`;
78
150
 
79
- console.log(`Downloading wm v${VERSION} for ${getPlatformKey()}...`);
80
- console.log(` ${url}`);
151
+ console.log(`\n WatchmenCLI v${VERSION} ${platformKey}`);
152
+ console.log(` ${url}\n`);
81
153
 
82
- // Download binary (URL redirects to pre-signed S3 URL)
83
154
  const binary = await download(url);
84
155
 
156
+ // Validate minimum binary size (real binaries are >5MB)
157
+ if (binary.length < 1024 * 1024) {
158
+ throw new Error(
159
+ `Downloaded file is only ${formatBytes(binary.length)} — expected >5MB.\n` +
160
+ ` This usually means the download URL returned an error page.\n` +
161
+ ` Try: npm cache clean --force && npm install -g watchmen-cli`
162
+ );
163
+ }
164
+
85
165
  // Write binary
86
166
  fs.mkdirSync(binDir, { recursive: true });
87
167
  fs.writeFileSync(binPath, binary);
88
- fs.chmodSync(binPath, 0o755);
89
168
 
90
- console.log(` Installed: ${binPath}`);
169
+ // chmod on Unix (no-op concept on Windows, but doesn't hurt)
170
+ if (!isWindows) {
171
+ fs.chmodSync(binPath, 0o755);
172
+ }
173
+
174
+ // Verify the binary runs
175
+ try {
176
+ execSync(`"${binPath}" version`, { encoding: "utf8", timeout: 10_000 });
177
+ console.log(` Installed successfully: ${binPath}\n`);
178
+ } catch (err) {
179
+ console.warn(
180
+ ` Warning: binary installed but verification failed.\n` +
181
+ ` Path: ${binPath}\n` +
182
+ ` You may need to run: wm doctor\n`
183
+ );
184
+ }
91
185
  }
92
186
 
93
187
  main().catch((err) => {
94
- console.error(`Failed to install wm: ${err.message}`);
188
+ console.error(`\n Failed to install wm: ${err.message}\n`);
189
+ console.error(
190
+ ` Troubleshooting:\n` +
191
+ ` 1. Check your internet connection\n` +
192
+ ` 2. Try: npm cache clean --force && npm install -g watchmen-cli\n` +
193
+ ` 3. Download manually: https://trywatchmen.cloud/docs/getting-started/installation\n`
194
+ );
95
195
  process.exit(1);
96
196
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "watchmen-cli",
3
- "version": "1.0.11",
4
- "description": "Developer environment intelligence platform 13 scanners, 24 MCP tools, zero dependencies",
3
+ "version": "1.0.13",
4
+ "description": "Capture, compare, and transfer your complete development environment",
5
5
  "main": "install.js",
6
6
  "keywords": [
7
7
  "cli",
@@ -23,7 +23,7 @@
23
23
  "license": "MIT",
24
24
  "author": "Cristiano Pereira da Silva Muniz, LLC",
25
25
  "bin": {
26
- "wm": "bin/wm"
26
+ "wm": "bin/wm.js"
27
27
  },
28
28
  "scripts": {
29
29
  "postinstall": "node install.js"
package/bin/wm DELETED
@@ -1,5 +0,0 @@
1
- #!/bin/sh
2
- # Stub — replaced by install.js postinstall with the actual binary.
3
- # If you see this message, run: npm rebuild watchmen-cli
4
- echo "wm binary not installed. Run: npm rebuild watchmen-cli" >&2
5
- exit 1