swytchcode 2.20.5 → 2.20.7

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/swytchcode.js +109 -40
  2. package/package.json +7 -7
package/bin/swytchcode.js CHANGED
@@ -205,6 +205,34 @@ function apiPost(urlStr, body) {
205
205
  });
206
206
  }
207
207
 
208
+ // describeSpawnError turns a failed spawnSync's `error` into an actionable
209
+ // message. On Windows, antivirus/EDR software quarantining or blocking an
210
+ // unsigned downloaded .exe is by far the most common cause of a launch
211
+ // failure here, and it surfaces through Node as a generic, unhelpful error:
212
+ // EPERM, or - most often - a bare "UNKNOWN" with no further detail, because
213
+ // the OS refused the spawn rather than reporting why. Node's own message for
214
+ // that case ("spawnSync ... UNKNOWN") gives the user nothing to act on, so we
215
+ // add the likely cause and a concrete next step instead of just relaying it.
216
+ function describeSpawnError(binPath, error) {
217
+ const base = `${error.message} (path: ${binPath})`;
218
+ if (process.platform !== 'win32') return base;
219
+
220
+ const fileIsGone = !fs.existsSync(binPath);
221
+ if (fileIsGone) {
222
+ return `${base}\n` +
223
+ ` The binary is missing from disk - antivirus software may have quarantined it after install.\n` +
224
+ ` Check your antivirus quarantine, then reinstall: npm install -g swytchcode`;
225
+ }
226
+ if (error.code === 'UNKNOWN' || error.code === 'EPERM' || error.code === 'EACCES') {
227
+ return `${base}\n` +
228
+ ` Windows refused to run this binary without saying why (code: ${error.code}) - this almost\n` +
229
+ ` always means antivirus or Windows Defender is blocking it, not a problem with swytchcode.\n` +
230
+ ` Check your antivirus/Defender quarantine and allow "${binPath}", then try again.\n` +
231
+ ` Details: https://docs.swytchcode.com/docs/`;
232
+ }
233
+ return base;
234
+ }
235
+
208
236
  // ─── Binary resolution (shared by normal pass-through AND onboarding) ────────
209
237
  function spawnBin(binPath, args, extraEnv = {}) {
210
238
  const env = { ...process.env, ...extraEnv };
@@ -255,7 +283,7 @@ const bareNpxLaunch =
255
283
  if (explicitBin && !bareNpxLaunch) {
256
284
  const result = spawnBin(explicitBin, process.argv.slice(2));
257
285
  if (result.error) {
258
- err(`swytchcode: failed to launch SWYTCHCODE_BIN="${explicitBin}": ${result.error.message}`);
286
+ err(`swytchcode: failed to launch SWYTCHCODE_BIN="${explicitBin}": ${describeSpawnError(explicitBin, result.error)}`);
259
287
  process.exit(1);
260
288
  }
261
289
  process.exit(exitCode(result));
@@ -367,12 +395,30 @@ const isNpx =
367
395
  binPath.includes('_npx') ||
368
396
  (process.env.npm_execpath || '').includes('npx');
369
397
 
398
+ // Top-level subcommands the Go CLI actually registers - see internal/cli/root.go's
399
+ // rootCmd.AddCommand calls (plus cobra's built-in "help"). Keep this in sync with
400
+ // that file: anything NOT in this set is treated below as a mistyped/invalid demo
401
+ // lookup rather than passed through, so a real subcommand missing from this list
402
+ // would silently stop working under npx (e.g. `npx swytchcode examples` would be
403
+ // swallowed into "isn't a demo tool" instead of running the actual command).
404
+ const KNOWN_SUBCOMMANDS = new Set([
405
+ 'init', 'get', 'add', 'bootstrap', 'sync', 'workflow', 'policy',
406
+ 'search', 'list', 'info', 'discover', 'plan',
407
+ 'exec', 'demo', 'examples',
408
+ 'login', 'logout', 'whoami', 'auth',
409
+ 'mcp', 'doctor', 'audit', 'config',
410
+ 'help',
411
+ ]);
412
+
370
413
  const userArgs = process.argv.slice(2);
371
414
  // Capture original arg BEFORE mutation
372
415
  const originalArg = process.argv[2] || '';
373
416
  const isCanonicalDemoArg = /^[\w]+\.[\w.]+$/.test(originalArg) && !originalArg.startsWith('-');
374
- // A single bare word under npx is a potential (invalid) demo lookup - don't pass through to cobra
375
- const isNpxBareWordArg = isNpx && userArgs.length === 1 && /^[\w]+$/.test(originalArg) && !originalArg.startsWith('-');
417
+ // A single bare word under npx is a potential (invalid) demo lookup - don't pass
418
+ // through to cobra - UNLESS it's a real subcommand, in which case it must reach
419
+ // the actual CLI like any other argument does.
420
+ const isNpxBareWordArg = isNpx && userArgs.length === 1 && /^[\w]+$/.test(originalArg) &&
421
+ !originalArg.startsWith('-') && !KNOWN_SUBCOMMANDS.has(originalArg);
376
422
  // Legacy support: npx swytchcode <canonical_id> → npx swytchcode exec <canonical_id>
377
423
  if (userArgs.length > 0 && /^[\w]+\.[\w.]+$/.test(userArgs[0]) && !userArgs[0].startsWith('-')) {
378
424
  userArgs.unshift('exec');
@@ -383,7 +429,15 @@ async function runOnboardingDirectDemo(canonicalId) {
383
429
  writeln();
384
430
  const demoResult = spawnBin(binPath, ['exec', canonicalId], { SWYTCHCODE_ONBOARDING: '1' });
385
431
  writeln();
386
- if (demoResult.status === 0) {
432
+ if (demoResult.error) {
433
+ // The binary itself failed to launch (e.g. antivirus quarantine) - this is
434
+ // not the same as "not a demo tool" and must not be reported as that.
435
+ writeln(` ${c.red}Could not launch swytchcode: ${describeSpawnError(binPath, demoResult.error)}${c.reset}`);
436
+ writeln();
437
+ writeln(` ${c.dim}Let's run a working demo instead.${c.reset}`);
438
+ writeln();
439
+ await runDemoSelection();
440
+ } else if (demoResult.status === 0) {
387
441
  writeln(` ${c.dim}Done - ran through Swytchcode.${c.reset}`);
388
442
  writeln();
389
443
  } else {
@@ -392,7 +446,7 @@ async function runOnboardingDirectDemo(canonicalId) {
392
446
  writeln();
393
447
  await runDemoSelection();
394
448
  }
395
- await runOnboardingSteps2to4();
449
+ await runOnboardingSteps1to3();
396
450
  }
397
451
 
398
452
  // ─── HTTP helpers (used by both canonical-demo and bare-npx onboarding paths) ─
@@ -440,7 +494,7 @@ const CARD_DESCRIPTION = 'Swytchcode sits between your agent and any API - auth,
440
494
  if (!isNpx || (userArgs.length > 0 && !isCanonicalDemoArg && !isNpxBareWordArg)) {
441
495
  const result = spawnBin(binPath, userArgs);
442
496
  if (result.error) {
443
- err(`swytchcode: failed to launch binary: ${result.error.message}`);
497
+ err(`swytchcode: failed to launch binary: ${describeSpawnError(binPath, result.error)}`);
444
498
  }
445
499
  process.exit(exitCode(result));
446
500
  }
@@ -461,7 +515,7 @@ if (isNpx && isCanonicalDemoArg) {
461
515
  writeln();
462
516
  Promise.resolve()
463
517
  .then(() => runDemoSelection())
464
- .then(() => runOnboardingSteps2to4())
518
+ .then(() => runOnboardingSteps1to3())
465
519
  .catch(e => {
466
520
  err(`\nswytchcode: onboarding error (${e.message}) - falling back to CLI.\n`);
467
521
  process.exit(1);
@@ -475,6 +529,16 @@ if (isNpx && isCanonicalDemoArg) {
475
529
  });
476
530
  }
477
531
 
532
+ // Maps a canonical demo tool ID (as returned by GET /demo/tools) to the short
533
+ // `swytchcode demo <name>` subcommand alias that actually runs it - see
534
+ // internal/cli/demo_commands.go's registered aliases. A tool with no entry
535
+ // here has no runnable `demo <name>` subcommand yet, so it's skipped in
536
+ // runDemoSelection below rather than launching a different demo by mistake.
537
+ const DEMO_TOOL_TO_SUBCOMMAND = {
538
+ 'stripe.create_payment': 'stripe',
539
+ 'fintech.compliance': 'fintech-compliance',
540
+ };
541
+
478
542
  async function runDemoSelection() {
479
543
  let demoTools = [];
480
544
  try {
@@ -503,12 +567,26 @@ async function runDemoSelection() {
503
567
  const chosen = await menuArrow('Choose a demo to run:', demoOptions);
504
568
 
505
569
  if (chosen !== '__skip__') {
570
+ const demoSubcommand = DEMO_TOOL_TO_SUBCOMMAND[chosen];
571
+ if (!demoSubcommand) {
572
+ // Backend added a demo tool with no matching `demo <name>` subcommand
573
+ // yet - skip rather than silently running a different demo instead.
574
+ writeln();
575
+ writeln(` ${c.yellow}${chosen} isn't runnable via this onboarding flow yet - skipping.${c.reset}`);
576
+ return;
577
+ }
506
578
  writeln();
507
- writeln(` ${c.gray}$ swytchcode demo stripe${c.reset}`);
508
- spawnBin(binPath, ['demo', 'stripe'], { SWYTCHCODE_ONBOARDING: '1' });
579
+ writeln(` ${c.gray}$ swytchcode demo ${demoSubcommand}${c.reset}`);
580
+ const demoResult = spawnBin(binPath, ['demo', demoSubcommand], { SWYTCHCODE_ONBOARDING: '1' });
509
581
  writeln();
510
- writeln(` ${c.dim}Done - ran through Swytchcode's demo account.${c.reset}`);
511
- writeln(` ${c.dim}Every integration runs the same way.${c.reset}`);
582
+ if (demoResult.error) {
583
+ writeln(` ${c.red}Could not launch swytchcode demo: ${describeSpawnError(binPath, demoResult.error)}${c.reset}`);
584
+ } else if (demoResult.status === 0) {
585
+ writeln(` ${c.dim}Done - ran through Swytchcode's demo account.${c.reset}`);
586
+ writeln(` ${c.dim}Every integration runs the same way.${c.reset}`);
587
+ } else {
588
+ writeln(` ${c.red}Demo exited with an error (status ${demoResult.status}).${c.reset}`);
589
+ }
512
590
  }
513
591
  }
514
592
 
@@ -580,7 +658,7 @@ function welcomeCard(cliVersion, auth) {
580
658
  }
581
659
 
582
660
  // cardBelow prints the sign-in line, cwd, and get-started hints. The sign-in
583
- // line reflects the real auth state so it stays consistent with the Step 3
661
+ // line reflects the real auth state so it stays consistent with the Step 2
584
662
  // skip-login logic (no "not signed in" when the user is actually authenticated).
585
663
  function cardBelow({ orange, dim, green, auth }) {
586
664
  writeln();
@@ -596,7 +674,6 @@ function cardBelow({ orange, dim, green, auth }) {
596
674
  const rows = [
597
675
  ['swytchcode init', 'set up this project'],
598
676
  ['swytchcode get <project>', 'fetch integration bundles'],
599
- ['swytchcode exec <canonical_id>', 'run a tool'],
600
677
  ];
601
678
  for (const [cmd, desc] of rows) writeln(` ${green('›')} ${cmd.padEnd(32)} ${dim(desc)}`);
602
679
  writeln();
@@ -622,18 +699,7 @@ async function runOnboarding() {
622
699
 
623
700
  welcomeCard(cliVersion, authStatus());
624
701
 
625
- // Divider between the welcome card/info and the interactive walkthrough,
626
- // matching the rules drawn between steps below.
627
- writeln();
628
- writeln(' ' + '─'.repeat(45));
629
- writeln();
630
-
631
- // ── Step 1: Demo ──────────────────────────────────────────────────────────
632
- writeln(`${c.bold} Step 1 of 4 - Try it${c.reset}`);
633
- writeln(`${c.dim} Run an API call through Swytchcode.${c.reset}`);
634
- writeln();
635
- await runDemoSelection();
636
- await runOnboardingSteps2to4();
702
+ await runOnboardingSteps1to3();
637
703
  }
638
704
 
639
705
  // isGloballyInstalled reports whether a real, persistent global `swytchcode`
@@ -688,12 +754,12 @@ function authStatus() {
688
754
  return { authed: false, label: '' };
689
755
  }
690
756
 
691
- async function runOnboardingSteps2to4() {
692
- // ── Step 2: Install ───────────────────────────────────────────────────────
757
+ async function runOnboardingSteps1to3() {
758
+ // ── Step 1: Install ───────────────────────────────────────────────────────
693
759
  writeln();
694
760
  writeln(' ' + '─'.repeat(45));
695
761
  writeln();
696
- writeln(`${c.bold} Step 2 of 4 - Install Swytchcode${c.reset}`);
762
+ writeln(`${c.bold} Step 1 of 3 - Install Swytchcode${c.reset}`);
697
763
 
698
764
  if (isGloballyInstalled()) {
699
765
  // Already installed - don't re-offer the install step.
@@ -731,11 +797,11 @@ async function runOnboardingSteps2to4() {
731
797
  }
732
798
  }
733
799
 
734
- // ── Step 3: Account sync ──────────────────────────────────────────────────
800
+ // ── Step 2: Account sync ──────────────────────────────────────────────────
735
801
  writeln();
736
802
  writeln(' ' + '─'.repeat(45));
737
803
  writeln();
738
- writeln(`${c.bold} Step 3 of 4 - Connect your account${c.reset}`);
804
+ writeln(`${c.bold} Step 2 of 3 - Connect your account${c.reset}`);
739
805
 
740
806
  const auth = authStatus();
741
807
  if (auth.authed) {
@@ -756,9 +822,11 @@ async function runOnboardingSteps2to4() {
756
822
  if (authChoice === 'login') {
757
823
  writeln(` ${c.dim}Opening browser for login ...${c.reset}`);
758
824
  writeln();
759
- spawnBin(binPath, ['login']);
760
- // A zero exit doesn't guarantee a session was created - confirm the token.
761
- if (authStatus().authed) {
825
+ const loginResult = spawnBin(binPath, ['login']);
826
+ if (loginResult.error) {
827
+ writeln(` ${c.red}Could not launch swytchcode login: ${describeSpawnError(binPath, loginResult.error)}${c.reset}`);
828
+ } else if (authStatus().authed) {
829
+ // A zero exit doesn't guarantee a session was created - confirm the token.
762
830
  writeln(` ${c.green}›${c.reset} logged in`);
763
831
  } else {
764
832
  writeln(` ${c.yellow}Login didn't complete.${c.reset}`);
@@ -769,8 +837,10 @@ async function runOnboardingSteps2to4() {
769
837
  writeln();
770
838
  // The browser flow handles both signup and login; verify a session
771
839
  // actually exists before claiming the account is connected.
772
- spawnBin(binPath, ['login']);
773
- if (authStatus().authed) {
840
+ const registerResult = spawnBin(binPath, ['login']);
841
+ if (registerResult.error) {
842
+ writeln(` ${c.red}Could not launch swytchcode login: ${describeSpawnError(binPath, registerResult.error)}${c.reset}`);
843
+ } else if (authStatus().authed) {
774
844
  writeln(` ${c.green}›${c.reset} account connected`);
775
845
  } else {
776
846
  writeln(` ${c.yellow}Account not connected yet.${c.reset}`);
@@ -782,11 +852,11 @@ async function runOnboardingSteps2to4() {
782
852
  }
783
853
  }
784
854
 
785
- // ── Step 4: Next action ───────────────────────────────────────────────────
855
+ // ── Step 3: Next action ───────────────────────────────────────────────────
786
856
  writeln();
787
857
  writeln(' ' + '─'.repeat(45));
788
858
  writeln();
789
- writeln(`${c.bold} Step 4 of 4 - What would you like to do next?${c.reset}`);
859
+ writeln(`${c.bold} Step 3 of 3 - What would you like to do next?${c.reset}`);
790
860
  writeln();
791
861
 
792
862
  const nextAction = await menuArrow('Choose an action:', [
@@ -802,14 +872,14 @@ async function runOnboardingSteps2to4() {
802
872
  writeln();
803
873
  const result = spawnBin(binPath, ['init']);
804
874
  if (result.error) {
805
- writeln(` ${c.red}Could not launch swytchcode init: ${result.error.message}${c.reset}`);
875
+ writeln(` ${c.red}Could not launch swytchcode init: ${describeSpawnError(binPath, result.error)}${c.reset}`);
806
876
  }
807
877
  } else if (nextAction === 'examples') {
808
878
  writeln(` ${c.dim}Loading examples ...${c.reset}`);
809
879
  writeln();
810
880
  const result = spawnBin(binPath, ['examples']);
811
881
  if (result.error) {
812
- writeln(` ${c.red}Could not launch swytchcode examples: ${result.error.message}${c.reset}`);
882
+ writeln(` ${c.red}Could not launch swytchcode examples: ${describeSpawnError(binPath, result.error)}${c.reset}`);
813
883
  }
814
884
  } else {
815
885
  writeln(` ${c.bold}You're all set.${c.reset}`);
@@ -818,7 +888,6 @@ async function runOnboardingSteps2to4() {
818
888
  writeln(` ${c.orange}swytchcode init${c.reset} ${c.dim}Set up a new project${c.reset}`);
819
889
  writeln(` ${c.orange}swytchcode examples${c.reset} ${c.dim}Browse example integrations${c.reset}`);
820
890
  writeln(` ${c.orange}swytchcode get <api>${c.reset} ${c.dim}Install an API integration${c.reset}`);
821
- writeln(` ${c.orange}swytchcode exec <id>${c.reset} ${c.dim}Execute an API method${c.reset}`);
822
891
  writeln(` ${c.orange}swytchcode --help${c.reset} ${c.dim}Full command reference${c.reset}`);
823
892
  }
824
893
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "swytchcode",
3
- "version": "2.20.5",
3
+ "version": "2.20.7",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://gitlab.com/swytchcode/cli.git"
@@ -28,11 +28,11 @@
28
28
  "README.md"
29
29
  ],
30
30
  "optionalDependencies": {
31
- "swytchcode-cli-darwin-arm64": "2.20.5",
32
- "swytchcode-cli-darwin-x64": "2.20.5",
33
- "swytchcode-cli-linux-arm64": "2.20.5",
34
- "swytchcode-cli-linux-x64": "2.20.5",
35
- "swytchcode-cli-win32-arm64": "2.20.5",
36
- "swytchcode-cli-win32-x64": "2.20.5"
31
+ "swytchcode-cli-darwin-arm64": "2.20.7",
32
+ "swytchcode-cli-darwin-x64": "2.20.7",
33
+ "swytchcode-cli-linux-arm64": "2.20.7",
34
+ "swytchcode-cli-linux-x64": "2.20.7",
35
+ "swytchcode-cli-win32-arm64": "2.20.7",
36
+ "swytchcode-cli-win32-x64": "2.20.7"
37
37
  }
38
38
  }