swytchcode 2.5.2 → 2.6.1

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 +125 -58
  2. package/package.json +7 -7
package/bin/swytchcode.js CHANGED
@@ -1,6 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
+ process.on('SIGINT', () => {
5
+ try { process.stdin.setRawMode(false); } catch (_) {} // prevent stuck raw mode on Windows CMD
6
+ process.stdout.write('\x1b[?25h'); // restore cursor
7
+ process.exit(130);
8
+ });
9
+
4
10
  const { spawnSync, execSync } = require('child_process');
5
11
  const fs = require('fs');
6
12
  const path = require('path');
@@ -45,7 +51,20 @@ function stripAnsi(str) {
45
51
  function ask(prompt) {
46
52
  return new Promise(resolve => {
47
53
  const iface = rl.createInterface({ input: process.stdin, output: process.stdout });
48
- iface.question(prompt, answer => { iface.close(); resolve(answer.trim()); });
54
+
55
+ function onClose() {
56
+ try { process.stdin.setRawMode(false); } catch (_) {}
57
+ process.stdout.write('\x1b[?25h');
58
+ process.exit(130);
59
+ }
60
+
61
+ iface.on('close', onClose);
62
+
63
+ iface.question(prompt, answer => {
64
+ iface.removeListener('close', onClose); // only removes this specific listener
65
+ iface.close();
66
+ resolve(answer.trim());
67
+ });
49
68
  });
50
69
  }
51
70
 
@@ -186,17 +205,29 @@ function apiPost(urlStr, body) {
186
205
  }
187
206
 
188
207
  // ─── Binary resolution (shared by normal pass-through AND onboarding) ────────
189
- function spawnBin(binPath, args) {
208
+ function spawnBin(binPath, args, extraEnv = {}) {
209
+ const env = { ...process.env, ...extraEnv };
210
+ let result;
190
211
  if (process.stdin.isTTY) {
191
- return spawnSync(binPath, args, { stdio: 'inherit' });
212
+ result = spawnSync(binPath, args, { stdio: 'inherit', env });
213
+ } else {
214
+ let stdinData;
215
+ try { stdinData = fs.readFileSync(0); }
216
+ catch (_) { stdinData = Buffer.alloc(0); }
217
+ result = spawnSync(binPath, args, {
218
+ stdio: ['pipe', 'inherit', 'inherit'],
219
+ input: stdinData,
220
+ env,
221
+ });
192
222
  }
193
- let stdinData;
194
- try { stdinData = fs.readFileSync(0); }
195
- catch (_) { stdinData = Buffer.alloc(0); }
196
- return spawnSync(binPath, args, {
197
- stdio: ['pipe', 'inherit', 'inherit'],
198
- input: stdinData,
199
- });
223
+
224
+ if (result.signal === 'SIGINT') {
225
+ try { process.stdin.setRawMode(false); } catch (_) {}
226
+ process.stdout.write('\x1b[?25h');
227
+ process.exit(130);
228
+ }
229
+
230
+ return result;
200
231
  }
201
232
 
202
233
  function exitCode(result) {
@@ -266,22 +297,47 @@ const isNpx =
266
297
  (process.env.npm_execpath || '').includes('npx');
267
298
 
268
299
  const userArgs = process.argv.slice(2);
269
-
300
+ // Capture original arg BEFORE mutation
301
+ const originalArg = process.argv[2] || '';
302
+ const isCanonicalDemoArg = /^[\w]+\.[\w.]+$/.test(originalArg) && !originalArg.startsWith('-');
270
303
  // Legacy support: npx swytchcode <canonical_id> → npx swytchcode exec <canonical_id>
271
304
  if (userArgs.length > 0 && /^[\w]+\.[\w.]+$/.test(userArgs[0]) && !userArgs[0].startsWith('-')) {
272
305
  userArgs.unshift('exec');
273
306
  }
307
+ async function runOnboardingDirectDemo(canonicalId) {
308
+ writeln();
309
+ writeln(` ${c.yellow}⚡ Running ${c.bold}${canonicalId}${c.reset}${c.yellow} live...${c.reset}`);
310
+ writeln();
311
+ const demoResult = spawnBin(binPath, ['exec', canonicalId], { SWYTCHCODE_ONBOARDING: '1' });
312
+ writeln();
313
+ if (demoResult.status === 0) {
314
+ writeln(` ${c.dim}That was a real API call through Swytchcode.${c.reset}`);
315
+ writeln();
316
+ } else {
317
+ writeln(` ${c.yellow}⚠ ${c.bold}${canonicalId}${c.reset}${c.yellow} isn't a valid demo tool.${c.reset}`);
318
+ writeln(` ${c.dim}Let's run a working demo instead.${c.reset}`);
319
+ writeln();
320
+ await runDemoSelection();
321
+ }
322
+ await runOnboardingSteps2to4();
323
+ }
274
324
 
275
- // ─── Normal pass-through (not npx, or npx with args) ────────────────────────
276
- if (!isNpx || userArgs.length > 0) {
325
+ // ─── Normal pass-through (not npx, or npx with non-demo args) ───────────────
326
+ if (!isNpx || (userArgs.length > 0 && !isCanonicalDemoArg)) {
277
327
  const result = spawnBin(binPath, userArgs);
278
328
  if (result.error) {
279
329
  err(`swytchcode: failed to launch binary: ${result.error.message}`);
280
- process.exit(1);
281
330
  }
282
331
  process.exit(exitCode(result));
283
332
  }
284
-
333
+ // ─── NPX with canonical ID: run demo directly then show onboarding ───────────
334
+ if (isNpx && isCanonicalDemoArg) {
335
+ runOnboardingDirectDemo(originalArg).catch(e => {
336
+ err(`\nswytchcode: onboarding error (${e.message}) — falling back to CLI.\n`);
337
+ const result = spawnBin(binPath, userArgs);
338
+ process.exit(exitCode(result));
339
+ });
340
+ } else {
285
341
  // ─── NPX ONBOARDING FLOW ─────────────────────────────────────────────────────
286
342
  // Only reached when: isNpx === true AND no args passed (bare `npx swytchcode`)
287
343
 
@@ -311,6 +367,46 @@ function apiGet(urlStr) {
311
367
  req.end();
312
368
  });
313
369
  }
370
+
371
+ }
372
+
373
+ async function runDemoSelection() {
374
+ let demoTools = [];
375
+ try {
376
+ const res = await apiGet(`${API_BASE}/demo/tools`);
377
+ if (res.status === 200 && Array.isArray(res.body.tools)) {
378
+ demoTools = res.body.tools;
379
+ }
380
+ } catch (_) {}
381
+
382
+ if (demoTools.length === 0) {
383
+ writeln(`${c.yellow} Demo unavailable right now — skipping to setup.${c.reset}`);
384
+ writeln();
385
+ return;
386
+ }
387
+
388
+ const friendlyName = {
389
+ 'stripe.create_payment': 'Stripe — Create a payment ($20.00)',
390
+ };
391
+
392
+ const demoOptions = demoTools.map(t => ({
393
+ label: friendlyName[t] || t,
394
+ value: t,
395
+ }));
396
+ demoOptions.push({ label: 'Skip demo', value: '__skip__' });
397
+
398
+ const chosen = await menuArrow('Choose a demo to run:', demoOptions);
399
+
400
+ if (chosen !== '__skip__') {
401
+ writeln();
402
+ writeln(` ${c.gray}$ swytchcode demo stripe${c.reset}`);
403
+ spawnBin(binPath, ['demo', 'stripe'], { SWYTCHCODE_ONBOARDING: '1' });
404
+ writeln();
405
+ writeln(` ${c.dim}That was a real API call. No credentials, no setup.${c.reset}`);
406
+ writeln(` ${c.dim}With Swytchcode, every API integration works exactly like this.${c.reset}`);
407
+ }
408
+ }
409
+
314
410
  async function runOnboarding() {
315
411
  let cliVersion = '0.0.0';
316
412
  try {
@@ -342,45 +438,11 @@ async function runOnboarding() {
342
438
  writeln(`${c.bold} Step 1 of 4 — See it in action${c.reset}`);
343
439
  writeln(`${c.dim} Run a live API call right now, no setup required.${c.reset}`);
344
440
  writeln();
441
+ await runDemoSelection();
442
+ await runOnboardingSteps2to4();
443
+ }
345
444
 
346
- // Fetch available demo tools
347
- let demoTools = [];
348
- try {
349
- const res = await apiGet(`${API_BASE}/demo/tools`);
350
- if (res.status === 200 && Array.isArray(res.body.tools)) {
351
- demoTools = res.body.tools;
352
- }
353
- } catch (_) {
354
- // silently fall through — skip demo if API is unreachable
355
- }
356
-
357
- if (demoTools.length === 0) {
358
- writeln(`${c.yellow} Demo unavailable right now — skipping to setup.${c.reset}`);
359
- writeln();
360
- } else {
361
- // Map tool IDs to friendly names
362
- const friendlyName = {
363
- 'stripe.create_payment': 'Stripe — Create a payment ($20.00)',
364
- };
365
-
366
- const demoOptions = demoTools.map(t => ({
367
- label: friendlyName[t] || t,
368
- value: t,
369
- }));
370
- demoOptions.push({ label: 'Skip demo', value: '__skip__' });
371
-
372
- const chosen = await menuArrow('Choose a demo to run:', demoOptions);
373
-
374
- if (chosen !== '__skip__') {
375
- writeln();
376
- writeln(` ${c.gray}$ swytchcode demo stripe${c.reset}`);
377
- spawnSync(binPath, ['demo', 'stripe'], { stdio: 'inherit' });
378
- writeln();
379
- writeln(` ${c.dim}That was a real API call. No credentials, no setup.${c.reset}`);
380
- writeln(` ${c.dim}With Swytchcode, every API integration works exactly like this.${c.reset}`);
381
- }
382
- }
383
-
445
+ async function runOnboardingSteps2to4() {
384
446
  // ── Step 2: Install ───────────────────────────────────────────────────────
385
447
  writeln();
386
448
  writeln(' ' + '─'.repeat(45));
@@ -409,6 +471,12 @@ async function runOnboarding() {
409
471
  execSync('npm install swytchcode', { stdio: 'pipe' });
410
472
  installSpin.stop('Installed successfully');
411
473
  } catch (e) {
474
+ if (e.signal === 'SIGINT') {
475
+ installSpin.stop('Cancelled');
476
+ try { process.stdin.setRawMode(false); } catch (_) {}
477
+ process.stdout.write('\x1b[?25h');
478
+ process.exit(130);
479
+ }
412
480
  installSpin.fail('Installation failed.');
413
481
  }
414
482
 
@@ -436,7 +504,7 @@ async function runOnboarding() {
436
504
  if (authChoice === 'login') {
437
505
  writeln(` ${c.dim}Opening browser for login ...${c.reset}`);
438
506
  writeln();
439
- const result = spawnSync(binPath, ['login'], { stdio: 'inherit' });
507
+ const result = spawnBin(binPath, ['login']);
440
508
  if (result.error || result.status !== 0) {
441
509
  writeln(` ${c.yellow}Login window closed or failed.${c.reset}`);
442
510
  writeln(` ${c.dim}You can log in later with:${c.reset} ${c.cyan}swytchcode login${c.reset}`);
@@ -447,7 +515,7 @@ async function runOnboarding() {
447
515
  writeln(` ${c.dim}Opening browser to create your account ...${c.reset}`);
448
516
  writeln();
449
517
  // login command handles both login and registration via the browser flow
450
- const result = spawnSync(binPath, ['login'], { stdio: 'inherit' });
518
+ const result = spawnBin(binPath, ['login']);
451
519
  if (result.error || result.status !== 0) {
452
520
  writeln(` ${c.yellow}Browser window closed or failed.${c.reset}`);
453
521
  writeln(` ${c.dim}Create your account at:${c.reset} ${c.cyan}https://swytchcode.com/signup${c.reset}`);
@@ -477,14 +545,14 @@ async function runOnboarding() {
477
545
  if (nextAction === 'init') {
478
546
  writeln(` ${c.dim}Starting project setup ...${c.reset}`);
479
547
  writeln();
480
- const result = spawnSync(binPath, ['init'], { stdio: 'inherit' });
548
+ const result = spawnBin(binPath, ['init']);
481
549
  if (result.error) {
482
550
  writeln(` ${c.red}Could not launch swytchcode init: ${result.error.message}${c.reset}`);
483
551
  }
484
552
  } else if (nextAction === 'examples') {
485
553
  writeln(` ${c.dim}Loading examples ...${c.reset}`);
486
554
  writeln();
487
- const result = spawnSync(binPath, ['examples'], { stdio: 'inherit' });
555
+ const result = spawnBin(binPath, ['examples']);
488
556
  if (result.error) {
489
557
  writeln(` ${c.red}Could not launch swytchcode examples: ${result.error.message}${c.reset}`);
490
558
  }
@@ -505,7 +573,6 @@ async function runOnboarding() {
505
573
  }
506
574
 
507
575
  runOnboarding().catch(e => {
508
- // If onboarding crashes for any reason, fall back to normal binary pass-through
509
576
  err(`\nswytchcode: onboarding error (${e.message}) — falling back to CLI.\n`);
510
577
  const result = spawnBin(binPath, userArgs);
511
578
  process.exit(exitCode(result));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "swytchcode",
3
- "version": "2.5.2",
3
+ "version": "2.6.1",
4
4
  "description": "Execution authority layer for API integrations",
5
5
  "keywords": [
6
6
  "api",
@@ -23,11 +23,11 @@
23
23
  "README.md"
24
24
  ],
25
25
  "optionalDependencies": {
26
- "swytchcode-cli-darwin-arm64": "2.5.2",
27
- "swytchcode-cli-darwin-x64": "2.5.2",
28
- "swytchcode-cli-linux-arm64": "2.5.2",
29
- "swytchcode-cli-linux-x64": "2.5.2",
30
- "swytchcode-cli-win32-arm64": "2.5.2",
31
- "swytchcode-cli-win32-x64": "2.5.2"
26
+ "swytchcode-cli-darwin-arm64": "2.6.1",
27
+ "swytchcode-cli-darwin-x64": "2.6.1",
28
+ "swytchcode-cli-linux-arm64": "2.6.1",
29
+ "swytchcode-cli-linux-x64": "2.6.1",
30
+ "swytchcode-cli-win32-arm64": "2.6.1",
31
+ "swytchcode-cli-win32-x64": "2.6.1"
32
32
  }
33
33
  }