squad-station 0.8.16 → 0.8.18

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 (2) hide show
  1. package/bin/run.js +138 -24
  2. package/package.json +1 -1
package/bin/run.js CHANGED
@@ -11,6 +11,8 @@ const subcommand = process.argv[2];
11
11
 
12
12
  if (subcommand === 'install') {
13
13
  install();
14
+ } else if (subcommand === 'uninstall') {
15
+ uninstall();
14
16
  } else {
15
17
  proxyToBinary();
16
18
  }
@@ -43,7 +45,7 @@ function install() {
43
45
 
44
46
  function installBinary() {
45
47
  // Binary version — may differ from npm package version
46
- var VERSION = '0.8.16';
48
+ var VERSION = '0.8.18';
47
49
  var REPO = 'thientranhung/squad-station';
48
50
 
49
51
  var isWindows = process.platform === 'win32';
@@ -73,6 +75,7 @@ function installBinary() {
73
75
  var result = spawnSync(destPath, ['--version'], { encoding: 'utf8' });
74
76
  if (result.stdout && result.stdout.includes(VERSION)) {
75
77
  console.log(' \x1b[32m✓\x1b[0m squad-station v' + VERSION + ' already installed at ' + destPath);
78
+ checkDuplicateBinary(destPath);
76
79
  return;
77
80
  }
78
81
  } catch (_) {
@@ -116,6 +119,9 @@ function installBinary() {
116
119
 
117
120
  // Verify the binary is actually callable via PATH
118
121
  verifyInPath(destPath, installDir);
122
+
123
+ // Check for duplicate binaries at other locations
124
+ checkDuplicateBinary(destPath);
119
125
  }
120
126
 
121
127
  // Find the best install directory that is already in the user's PATH.
@@ -126,29 +132,34 @@ function findBestInstallDir() {
126
132
  var pathSep = isWindows ? ';' : ':';
127
133
  var pathDirs = (process.env.PATH || '').split(pathSep).filter(Boolean);
128
134
 
129
- // Candidate directories in preference order
135
+ // Primary: ~/.squad/bin our own directory, does not need to be on PATH
136
+ var squadBin = path.join(home, '.squad', 'bin');
137
+ try {
138
+ fs.mkdirSync(squadBin, { recursive: true });
139
+ fs.accessSync(squadBin, fs.constants.W_OK);
140
+ return squadBin;
141
+ } catch (_) {
142
+ // Fall through to other candidates
143
+ }
144
+
145
+ // Secondary candidates — must be on PATH and writable
130
146
  var candidates = isWindows
131
147
  ? [
132
148
  path.join(home, '.local', 'bin'),
133
149
  path.join(home, 'AppData', 'Local', 'Microsoft', 'WindowsApps'),
134
150
  ]
135
151
  : [
136
- '/usr/local/bin',
137
152
  path.join(home, '.local', 'bin'),
138
- path.join(home, '.cargo', 'bin'),
139
- '/opt/homebrew/bin',
153
+ '/usr/local/bin',
140
154
  ];
141
155
 
142
- // Pick the first candidate that is already in PATH and is writable
143
156
  for (var i = 0; i < candidates.length; i++) {
144
157
  var dir = candidates[i];
145
- // Check if this directory is in PATH
146
158
  var inPath = pathDirs.some(function(p) {
147
159
  return path.resolve(p) === path.resolve(dir);
148
160
  });
149
161
  if (!inPath) continue;
150
162
 
151
- // Check if writable (create if needed)
152
163
  try {
153
164
  fs.mkdirSync(dir, { recursive: true });
154
165
  fs.accessSync(dir, fs.constants.W_OK);
@@ -158,10 +169,9 @@ function findBestInstallDir() {
158
169
  }
159
170
  }
160
171
 
161
- // Fallback: ~/.local/bin (may not be in PATH — we'll warn later)
162
- var fallback = path.join(home, '.local', 'bin');
163
- fs.mkdirSync(fallback, { recursive: true });
164
- return fallback;
172
+ // Fallback: ~/.squad/bin (already created above if writable)
173
+ fs.mkdirSync(squadBin, { recursive: true });
174
+ return squadBin;
165
175
  }
166
176
 
167
177
  // Verify the installed binary is callable. If not, print PATH instructions.
@@ -175,33 +185,137 @@ function verifyInPath(destPath, installDir) {
175
185
  return;
176
186
  }
177
187
 
178
- // Not in PATH — print platform-specific instructions
188
+ // Not in PATH — print instructions
179
189
  console.log('');
180
190
  console.log(' \x1b[33m⚠ squad-station is not in your PATH\x1b[0m');
181
191
  console.log(' The binary was installed to: \x1b[36m' + installDir + '\x1b[0m');
182
192
  console.log('');
183
- console.log(' Add it to your PATH:');
184
- console.log('');
185
193
 
186
- if (process.platform === 'darwin') {
187
- console.log(' \x1b[2m# macOS (zsh) — add to ~/.zshrc:\x1b[0m');
188
- console.log(' \x1b[36mexport PATH="' + installDir + ':$PATH"\x1b[0m');
194
+ if (isWindows) {
195
+ console.log(' Add to your PATH:');
189
196
  console.log('');
190
- console.log(' Then reload: \x1b[36msource ~/.zshrc\x1b[0m');
191
- } else if (isWindows) {
192
197
  console.log(' \x1b[2m# Windows (PowerShell) — run as Administrator:\x1b[0m');
193
198
  console.log(' \x1b[36m[Environment]::SetEnvironmentVariable("Path",\x1b[0m');
194
199
  console.log(' \x1b[36m [Environment]::GetEnvironmentVariable("Path", "User") + ";' + installDir + '", "User")\x1b[0m');
195
200
  console.log('');
196
201
  console.log(' Then restart your terminal.');
197
202
  } else {
198
- // Linux
199
- console.log(' \x1b[2m# Linux (bash) add to ~/.bashrc:\x1b[0m');
200
- console.log(' \x1b[36mexport PATH="' + installDir + ':$PATH"\x1b[0m');
203
+ var shellProfile = process.platform === 'darwin' ? '~/.zshrc' : '~/.bashrc';
204
+ console.log(' Add to your shell profile: \x1b[36mexport PATH="$HOME/.squad/bin:$PATH"\x1b[0m');
205
+ console.log(' Then restart your terminal or run: \x1b[36msource ' + shellProfile + '\x1b[0m');
206
+ }
207
+ console.log('');
208
+ }
209
+
210
+ // Check if another squad-station binary exists at a different path than where we installed.
211
+ function checkDuplicateBinary(installedPath) {
212
+ var isWindows = process.platform === 'win32';
213
+ var checkCmd = isWindows ? 'where' : 'which';
214
+ var result = spawnSync(checkCmd, ['squad-station'], { encoding: 'utf8' });
215
+
216
+ if (result.status !== 0 || !result.stdout || !result.stdout.trim()) {
217
+ return; // not in PATH at all — verifyInPath already warned
218
+ }
219
+
220
+ var resolvedWhich = fs.realpathSync(result.stdout.trim());
221
+ var resolvedInstalled = fs.realpathSync(installedPath);
222
+
223
+ if (resolvedWhich !== resolvedInstalled) {
224
+ console.log('');
225
+ console.log(' \x1b[33m⚠ Another squad-station found at: ' + result.stdout.trim() + '\x1b[0m');
226
+ console.log(' This version will be used instead of the one just installed.');
227
+ console.log(' Remove it to avoid conflicts: \x1b[36mrm ' + result.stdout.trim() + '\x1b[0m');
201
228
  console.log('');
202
- console.log(' Then reload: \x1b[36msource ~/.bashrc\x1b[0m');
229
+ }
230
+ }
231
+
232
+ // ── Uninstall ───────────────────────────────────────────────────────
233
+ // Find and remove all squad-station binaries from known locations.
234
+
235
+ function uninstall() {
236
+ var home = process.env.HOME || process.env.USERPROFILE || '';
237
+ var isWindows = process.platform === 'win32';
238
+ var binaryName = isWindows ? 'squad-station.exe' : 'squad-station';
239
+
240
+ // Search directories: current install locations + legacy locations
241
+ var searchDirs = isWindows
242
+ ? [
243
+ path.join(home, '.squad', 'bin'),
244
+ path.join(home, '.local', 'bin'),
245
+ path.join(home, 'AppData', 'Local', 'Microsoft', 'WindowsApps'),
246
+ ]
247
+ : [
248
+ path.join(home, '.squad', 'bin'),
249
+ path.join(home, '.local', 'bin'),
250
+ '/usr/local/bin',
251
+ path.join(home, '.cargo', 'bin'),
252
+ '/opt/homebrew/bin',
253
+ ];
254
+
255
+ console.log('\n\x1b[32m══════════════════════════════════\x1b[0m');
256
+ console.log(' \x1b[1mSquad Station Uninstall\x1b[0m');
257
+ console.log('\x1b[32m══════════════════════════════════\x1b[0m\n');
258
+
259
+ // Find all existing binaries
260
+ var found = [];
261
+ for (var i = 0; i < searchDirs.length; i++) {
262
+ var binPath = path.join(searchDirs[i], binaryName);
263
+ if (fs.existsSync(binPath)) {
264
+ var version = '(unknown version)';
265
+ try {
266
+ var result = spawnSync(binPath, ['--version'], { encoding: 'utf8', timeout: 5000 });
267
+ if (result.stdout && result.stdout.trim()) {
268
+ version = result.stdout.trim();
269
+ }
270
+ } catch (_) {}
271
+ found.push({ path: binPath, version: version });
272
+ }
273
+ }
274
+
275
+ if (found.length === 0) {
276
+ console.log(' No squad-station binaries found.\n');
277
+ return;
278
+ }
279
+
280
+ console.log(' Found ' + found.length + ' binary(ies):\n');
281
+ for (var j = 0; j < found.length; j++) {
282
+ console.log(' \x1b[36m' + found[j].path + '\x1b[0m ' + found[j].version);
203
283
  }
204
284
  console.log('');
285
+
286
+ // Ask for confirmation (read single line from stdin)
287
+ process.stdout.write(' Remove these binaries? [y/N] ');
288
+ var buf = Buffer.alloc(128);
289
+ var bytesRead = 0;
290
+ try {
291
+ bytesRead = fs.readSync(0, buf, 0, buf.length);
292
+ } catch (_) {
293
+ console.log('\n Aborted.\n');
294
+ return;
295
+ }
296
+ var answer = buf.toString('utf8', 0, bytesRead).trim().toLowerCase();
297
+
298
+ if (answer !== 'y' && answer !== 'yes') {
299
+ console.log(' Aborted.\n');
300
+ return;
301
+ }
302
+
303
+ // Delete each binary
304
+ var removed = 0;
305
+ for (var k = 0; k < found.length; k++) {
306
+ try {
307
+ fs.unlinkSync(found[k].path);
308
+ console.log(' \x1b[32m✓\x1b[0m Removed ' + found[k].path);
309
+ removed++;
310
+ } catch (e) {
311
+ console.log(' \x1b[31m✗\x1b[0m Failed to remove ' + found[k].path + ': ' + e.message);
312
+ }
313
+ }
314
+
315
+ console.log('');
316
+ if (removed > 0) {
317
+ console.log(' Uninstalled. You can also remove ~/.squad/bin from your PATH if no longer needed.\n');
318
+ }
205
319
  }
206
320
 
207
321
  function scaffoldProject(force) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "squad-station",
3
- "version": "0.8.16",
3
+ "version": "0.8.18",
4
4
  "description": "Message routing and orchestration for AI agent squads",
5
5
  "repository": {
6
6
  "type": "git",