swytchcode 2.14.0-beta.1 → 2.14.0-beta.3
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/bin/swytchcode.js +220 -190
- package/package.json +7 -7
package/bin/swytchcode.js
CHANGED
|
@@ -22,6 +22,7 @@ const c = {
|
|
|
22
22
|
green: '\x1b[32m',
|
|
23
23
|
yellow: '\x1b[33m',
|
|
24
24
|
cyan: '\x1b[36m',
|
|
25
|
+
orange: '\x1b[38;5;214m', // brand orange (matches internal/output banner 256 fallback)
|
|
25
26
|
white: '\x1b[37m',
|
|
26
27
|
gray: '\x1b[90m',
|
|
27
28
|
bgGreen: '\x1b[42m',
|
|
@@ -33,7 +34,7 @@ function write(msg) { process.stdout.write(msg); }
|
|
|
33
34
|
function writeln(msg = '') { process.stdout.write(msg + '\n'); }
|
|
34
35
|
function err(msg) { process.stderr.write(msg + '\n'); }
|
|
35
36
|
|
|
36
|
-
function box(lines, colour = c.
|
|
37
|
+
function box(lines, colour = c.orange) {
|
|
37
38
|
const width = Math.max(...lines.map(l => stripAnsi(l).length)) + 4;
|
|
38
39
|
const bar = colour + '─'.repeat(width) + c.reset;
|
|
39
40
|
writeln(bar);
|
|
@@ -77,7 +78,7 @@ function menuArrow(prompt, options) {
|
|
|
77
78
|
writeln(c.bold + prompt + c.reset);
|
|
78
79
|
writeln();
|
|
79
80
|
options.forEach((o, i) => {
|
|
80
|
-
writeln(` ${c.
|
|
81
|
+
writeln(` ${c.orange}${i + 1}${c.reset} ${stripAnsi(o.label)}`);
|
|
81
82
|
});
|
|
82
83
|
writeln();
|
|
83
84
|
while (true) {
|
|
@@ -103,7 +104,7 @@ function menuArrow(prompt, options) {
|
|
|
103
104
|
writeln();
|
|
104
105
|
options.forEach((o, i) => {
|
|
105
106
|
if (i === selectedIndex) {
|
|
106
|
-
writeln(` ${c.
|
|
107
|
+
writeln(` ${c.orange}❯${c.reset} ${c.bold}${c.green}${stripAnsi(o.label)}${c.reset}`);
|
|
107
108
|
} else {
|
|
108
109
|
writeln(` ${c.dim}${stripAnsi(o.label)}${c.reset}`);
|
|
109
110
|
}
|
|
@@ -160,16 +161,16 @@ function spinner(msg) {
|
|
|
160
161
|
const frames = ['⠋','⠙','⠹','⠸','⠼','⠴','⠦','⠧','⠇','⠏'];
|
|
161
162
|
let i = 0;
|
|
162
163
|
const iv = setInterval(() => {
|
|
163
|
-
process.stdout.write(`\r${c.
|
|
164
|
+
process.stdout.write(`\r${c.orange}${frames[i++ % frames.length]}${c.reset} ${msg}`);
|
|
164
165
|
}, 80);
|
|
165
166
|
return {
|
|
166
167
|
stop(doneMsg) {
|
|
167
168
|
clearInterval(iv);
|
|
168
|
-
process.stdout.write(`\r${c.green}›${c.reset} ${doneMsg}\n`);
|
|
169
|
+
process.stdout.write(`\r\x1b[K${c.green}›${c.reset} ${doneMsg}\n`);
|
|
169
170
|
},
|
|
170
171
|
fail(failMsg) {
|
|
171
172
|
clearInterval(iv);
|
|
172
|
-
process.stdout.write(`\r${c.red}×${c.reset} ${failMsg}\n`);
|
|
173
|
+
process.stdout.write(`\r\x1b[K${c.red}×${c.reset} ${failMsg}\n`);
|
|
173
174
|
},
|
|
174
175
|
};
|
|
175
176
|
}
|
|
@@ -242,9 +243,16 @@ function exitCode(result) {
|
|
|
242
243
|
return 1;
|
|
243
244
|
}
|
|
244
245
|
|
|
245
|
-
// Explicit override: SWYTCHCODE_BIN=/path/to/binary bypasses platform resolution
|
|
246
|
+
// Explicit override: SWYTCHCODE_BIN=/path/to/binary bypasses platform resolution
|
|
247
|
+
// and passes straight through to that binary. The one exception is a bare
|
|
248
|
+
// npx-style launch (npm_command=exec / npx, with no args): there we keep the
|
|
249
|
+
// override binary but skip the passthrough so the onboarding still runs (using
|
|
250
|
+
// that binary for its subprocess calls), which makes onboarding testable locally.
|
|
246
251
|
const explicitBin = (process.env.SWYTCHCODE_BIN || '').trim();
|
|
247
|
-
|
|
252
|
+
const bareNpxLaunch =
|
|
253
|
+
process.argv.length <= 2 &&
|
|
254
|
+
((process.env.npm_command || '') === 'exec' || (process.env.npm_execpath || '').includes('npx'));
|
|
255
|
+
if (explicitBin && !bareNpxLaunch) {
|
|
248
256
|
const result = spawnBin(explicitBin, process.argv.slice(2));
|
|
249
257
|
if (result.error) {
|
|
250
258
|
err(`swytchcode: failed to launch SWYTCHCODE_BIN="${explicitBin}": ${result.error.message}`);
|
|
@@ -253,40 +261,38 @@ if (explicitBin) {
|
|
|
253
261
|
process.exit(exitCode(result));
|
|
254
262
|
}
|
|
255
263
|
|
|
256
|
-
const PLATFORM_PACKAGES = {
|
|
257
|
-
'darwin-arm64': 'swytchcode-cli-darwin-arm64',
|
|
258
|
-
'darwin-x64': 'swytchcode-cli-darwin-x64',
|
|
259
|
-
'linux-arm64': 'swytchcode-cli-linux-arm64',
|
|
260
|
-
'linux-x64': 'swytchcode-cli-linux-x64',
|
|
261
|
-
'win32-arm64': 'swytchcode-cli-win32-arm64',
|
|
262
|
-
'win32-x64': 'swytchcode-cli-win32-x64',
|
|
263
|
-
};
|
|
264
|
-
|
|
265
|
-
const platform = process.platform;
|
|
266
|
-
const arch = process.arch;
|
|
267
|
-
const key = `${platform}-${arch}`;
|
|
268
|
-
const pkg = PLATFORM_PACKAGES[key];
|
|
269
|
-
|
|
270
|
-
if (!pkg) {
|
|
271
|
-
err(
|
|
272
|
-
`swytchcode: unsupported platform "${key}".\n` +
|
|
273
|
-
`Supported: ${Object.keys(PLATFORM_PACKAGES).join(', ')}\n` +
|
|
274
|
-
`Install via curl instead: https://cli.swytchcode.com`
|
|
275
|
-
);
|
|
276
|
-
process.exit(1);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
const isWindows = platform === 'win32';
|
|
280
|
-
const binName = isWindows ? 'swytchcode.exe' : 'swytchcode';
|
|
281
264
|
let binPath;
|
|
282
|
-
|
|
283
|
-
binPath =
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
265
|
+
if (explicitBin) {
|
|
266
|
+
binPath = explicitBin;
|
|
267
|
+
} else {
|
|
268
|
+
const PLATFORM_PACKAGES = {
|
|
269
|
+
'darwin-arm64': 'swytchcode-cli-darwin-arm64',
|
|
270
|
+
'darwin-x64': 'swytchcode-cli-darwin-x64',
|
|
271
|
+
'linux-arm64': 'swytchcode-cli-linux-arm64',
|
|
272
|
+
'linux-x64': 'swytchcode-cli-linux-x64',
|
|
273
|
+
'win32-arm64': 'swytchcode-cli-win32-arm64',
|
|
274
|
+
'win32-x64': 'swytchcode-cli-win32-x64',
|
|
275
|
+
};
|
|
276
|
+
const key = `${process.platform}-${process.arch}`;
|
|
277
|
+
const pkg = PLATFORM_PACKAGES[key];
|
|
278
|
+
if (!pkg) {
|
|
279
|
+
err(
|
|
280
|
+
`swytchcode: unsupported platform "${key}".\n` +
|
|
281
|
+
`Supported: ${Object.keys(PLATFORM_PACKAGES).join(', ')}\n` +
|
|
282
|
+
`Install via curl instead: https://cli.swytchcode.com`
|
|
283
|
+
);
|
|
284
|
+
process.exit(1);
|
|
285
|
+
}
|
|
286
|
+
const binName = process.platform === 'win32' ? 'swytchcode.exe' : 'swytchcode';
|
|
287
|
+
try {
|
|
288
|
+
binPath = require.resolve(`${pkg}/bin/${binName}`);
|
|
289
|
+
} catch (_) {
|
|
290
|
+
err(
|
|
291
|
+
`swytchcode: could not find the binary for "${key}".\n` +
|
|
292
|
+
`Try reinstalling: npm install -g swytchcode`
|
|
293
|
+
);
|
|
294
|
+
process.exit(1);
|
|
295
|
+
}
|
|
290
296
|
}
|
|
291
297
|
|
|
292
298
|
// Detect npx
|
|
@@ -308,15 +314,15 @@ if (userArgs.length > 0 && /^[\w]+\.[\w.]+$/.test(userArgs[0]) && !userArgs[0].s
|
|
|
308
314
|
}
|
|
309
315
|
async function runOnboardingDirectDemo(canonicalId) {
|
|
310
316
|
writeln();
|
|
311
|
-
writeln(` ${c.
|
|
317
|
+
writeln(` ${c.dim}Running ${c.reset}${c.bold}${canonicalId}${c.reset}${c.dim} ...${c.reset}`);
|
|
312
318
|
writeln();
|
|
313
319
|
const demoResult = spawnBin(binPath, ['exec', canonicalId], { SWYTCHCODE_ONBOARDING: '1' });
|
|
314
320
|
writeln();
|
|
315
321
|
if (demoResult.status === 0) {
|
|
316
|
-
writeln(` ${c.dim}
|
|
322
|
+
writeln(` ${c.dim}Done — ran through Swytchcode.${c.reset}`);
|
|
317
323
|
writeln();
|
|
318
324
|
} else {
|
|
319
|
-
writeln(` ${c.
|
|
325
|
+
writeln(` ${c.dim}${c.bold}${canonicalId}${c.reset}${c.dim} isn't a demo tool — running a sample instead.${c.reset}`);
|
|
320
326
|
writeln(` ${c.dim}Let's run a working demo instead.${c.reset}`);
|
|
321
327
|
writeln();
|
|
322
328
|
await runDemoSelection();
|
|
@@ -351,6 +357,20 @@ function apiGet(urlStr) {
|
|
|
351
357
|
});
|
|
352
358
|
}
|
|
353
359
|
|
|
360
|
+
// ─── Welcome card data (mirrors internal/output/banner.go). Declared before the
|
|
361
|
+
// dispatch below because runOnboarding() runs at module-load time and must not
|
|
362
|
+
// hit a temporal-dead-zone on these consts. ─────────────────────────────────
|
|
363
|
+
const CARD_WORDMARK = [
|
|
364
|
+
'███████╗██╗ ██╗██╗ ██╗████████╗ ██████╗██╗ ██╗ ██████╗ ██████╗ ██████╗ ███████╗',
|
|
365
|
+
'██╔════╝██║ ██║╚██╗ ██╔╝╚══██╔══╝██╔════╝██║ ██║██╔════╝██╔═══██╗██╔══██╗██╔════╝',
|
|
366
|
+
'███████╗██║ █╗ ██║ ╚████╔╝ ██║ ██║ ███████║██║ ██║ ██║██║ ██║█████╗',
|
|
367
|
+
'╚════██║██║███╗██║ ╚██╔╝ ██║ ██║ ██╔══██║██║ ██║ ██║██║ ██║██╔══╝',
|
|
368
|
+
'███████║╚███╔███╔╝ ██║ ██║ ╚██████╗██║ ██║╚██████╗╚██████╔╝██████╔╝███████╗',
|
|
369
|
+
'╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝',
|
|
370
|
+
];
|
|
371
|
+
const CARD_TAGLINE = 'Your agent calls an API. We make sure it goes through.';
|
|
372
|
+
const CARD_DESCRIPTION = 'Swytchcode sits between your agent and any API — auth, retries, idempotency, and schema validation, handled. Run a command below, or swytchcode --help for the full list.';
|
|
373
|
+
|
|
354
374
|
// ─── Normal pass-through (not npx, or npx with non-demo args) ───────────────
|
|
355
375
|
if (!isNpx || (userArgs.length > 0 && !isCanonicalDemoArg && !isNpxBareWordArg)) {
|
|
356
376
|
const result = spawnBin(binPath, userArgs);
|
|
@@ -371,7 +391,7 @@ if (isNpx && isCanonicalDemoArg) {
|
|
|
371
391
|
} else if (isNpxBareWordArg) {
|
|
372
392
|
// Single bare word (e.g. "abcd") — not a valid canonical ID; show message then onboard
|
|
373
393
|
writeln();
|
|
374
|
-
writeln(` ${c.
|
|
394
|
+
writeln(` ${c.dim}${c.bold}${originalArg}${c.reset}${c.dim} isn't a demo tool — IDs look like ${c.reset}${c.bold}provider.method${c.reset}`);
|
|
375
395
|
writeln(` ${c.dim}Let's run a working demo instead.${c.reset}`);
|
|
376
396
|
writeln();
|
|
377
397
|
Promise.resolve()
|
|
@@ -422,40 +442,12 @@ async function runDemoSelection() {
|
|
|
422
442
|
writeln(` ${c.gray}$ swytchcode demo stripe${c.reset}`);
|
|
423
443
|
spawnBin(binPath, ['demo', 'stripe'], { SWYTCHCODE_ONBOARDING: '1' });
|
|
424
444
|
writeln();
|
|
425
|
-
writeln(` ${c.dim}
|
|
426
|
-
writeln(` ${c.dim}
|
|
445
|
+
writeln(` ${c.dim}Done — ran through Swytchcode's demo account.${c.reset}`);
|
|
446
|
+
writeln(` ${c.dim}Every integration runs the same way.${c.reset}`);
|
|
427
447
|
}
|
|
428
448
|
}
|
|
429
449
|
|
|
430
|
-
// ─── Welcome card (
|
|
431
|
-
const CARD_WORDMARK = [
|
|
432
|
-
'███████╗██╗ ██╗██╗ ██╗████████╗ ██████╗██╗ ██╗ ██████╗ ██████╗ ██████╗ ███████╗',
|
|
433
|
-
'██╔════╝██║ ██║╚██╗ ██╔╝╚══██╔══╝██╔════╝██║ ██║██╔════╝██╔═══██╗██╔══██╗██╔════╝',
|
|
434
|
-
'███████╗██║ █╗ ██║ ╚████╔╝ ██║ ██║ ███████║██║ ██║ ██║██║ ██║█████╗',
|
|
435
|
-
'╚════██║██║███╗██║ ╚██╔╝ ██║ ██║ ██╔══██║██║ ██║ ██║██║ ██║██╔══╝',
|
|
436
|
-
'███████║╚███╔███╔╝ ██║ ██║ ╚██████╗██║ ██║╚██████╗╚██████╔╝██████╔╝███████╗',
|
|
437
|
-
'╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝',
|
|
438
|
-
];
|
|
439
|
-
const CARD_MARK = [
|
|
440
|
-
' ▄▄▄█████████▄▄',
|
|
441
|
-
' ▄▄███▀▀▀ ▀▀▀███▄',
|
|
442
|
-
' ▄███▀',
|
|
443
|
-
' ███ ▄████████▀',
|
|
444
|
-
' ██▀ █████████',
|
|
445
|
-
' ██▀ ▄████████▀',
|
|
446
|
-
'███ ▄████████▀',
|
|
447
|
-
'██ █████████',
|
|
448
|
-
'██ ▄██████████████▀',
|
|
449
|
-
'██ ▄██████████████▀',
|
|
450
|
-
'▀██ ██████▀▄█████▀',
|
|
451
|
-
' ██▄ ▄████▀',
|
|
452
|
-
' ██▄ ▄████▀',
|
|
453
|
-
' ▀██▄ ▄██▀ ▄',
|
|
454
|
-
' ▀▀▀ ▄█▀▀ ▄▄███',
|
|
455
|
-
' ▄▀ ▄▄▄▄▄▄▄▄▄████▀▀',
|
|
456
|
-
' ▀ ▀▀████████▀▀',
|
|
457
|
-
];
|
|
458
|
-
const CARD_TAGLINE = 'Your agent calls an API. We make sure it goes through.';
|
|
450
|
+
// ─── Welcome card render helpers (data consts are hoisted above the dispatch) ─
|
|
459
451
|
|
|
460
452
|
function rep(ch, n) { return n > 0 ? ch.repeat(n) : ''; }
|
|
461
453
|
function vlen(s) { return [...s].length; }
|
|
@@ -463,20 +455,38 @@ function padRows(rows) {
|
|
|
463
455
|
const w = Math.max(...rows.map(vlen));
|
|
464
456
|
return { rows: rows.map(r => r + rep(' ', w - vlen(r))), w };
|
|
465
457
|
}
|
|
458
|
+
// wrapText greedily word-wraps s to the given visible width (in code points).
|
|
459
|
+
function wrapText(s, width) {
|
|
460
|
+
if (width < 1) width = 1;
|
|
461
|
+
const lines = [];
|
|
462
|
+
let cur = '';
|
|
463
|
+
for (const word of s.split(/\s+/).filter(Boolean)) {
|
|
464
|
+
if (cur === '') cur = word;
|
|
465
|
+
else if (vlen(cur) + 1 + vlen(word) <= width) cur += ' ' + word;
|
|
466
|
+
else { lines.push(cur); cur = word; }
|
|
467
|
+
}
|
|
468
|
+
if (cur !== '') lines.push(cur);
|
|
469
|
+
return lines;
|
|
470
|
+
}
|
|
471
|
+
// writeDescRows renders the wrapped description inside a framed box (boxRow closes
|
|
472
|
+
// over innerW), with the "swytchcode --help" hint highlighted.
|
|
473
|
+
function writeDescRows(boxRow, orange, contentW, margin) {
|
|
474
|
+
for (const ln of wrapText(CARD_DESCRIPTION, contentW)) {
|
|
475
|
+
const colored = ln.replace('swytchcode --help', orange('swytchcode --help'));
|
|
476
|
+
writeln(boxRow(rep(' ', margin) + colored, margin + vlen(ln)));
|
|
477
|
+
}
|
|
478
|
+
}
|
|
466
479
|
|
|
467
|
-
// welcomeCard renders the
|
|
468
|
-
// wordmark
|
|
469
|
-
//
|
|
480
|
+
// welcomeCard renders the universal welcome banner: the "swytchcode" figlet
|
|
481
|
+
// wordmark, version, tagline, and wrapped description inside a framed box — no
|
|
482
|
+
// bolt logo. Mirrors wordmarkBanner in internal/output/banner.go so npx
|
|
483
|
+
// onboarding and the installed CLI look identical (single form, no tiering).
|
|
470
484
|
function welcomeCard(cliVersion) {
|
|
471
485
|
const isTTY = !!process.stdout.isTTY;
|
|
472
|
-
const width = process.stdout.columns || 0;
|
|
473
486
|
const ct = process.env.COLORTERM || '';
|
|
474
487
|
const truecolor = ct === 'truecolor' || ct === '24bit';
|
|
475
488
|
const noColor = !!process.env.NO_COLOR || !!process.env.CI;
|
|
476
489
|
const useColor = !noColor && isTTY;
|
|
477
|
-
const utf8 = process.platform === 'win32'
|
|
478
|
-
? (!!process.env.WT_SESSION || process.env.TERM_PROGRAM === 'vscode' || process.env.ConEmuANSI === 'ON')
|
|
479
|
-
: /utf-?8/i.test(process.env.LC_ALL || process.env.LC_CTYPE || process.env.LANG || '');
|
|
480
490
|
|
|
481
491
|
const orange = s => !useColor ? s : (truecolor ? `\x1b[38;2;245;148;30m${s}\x1b[0m` : `\x1b[38;5;214m${s}\x1b[0m`);
|
|
482
492
|
const frame = s => !useColor ? s : (truecolor ? `\x1b[38;2;122;78;24m${s}\x1b[0m` : `\x1b[38;5;214m${s}\x1b[0m`);
|
|
@@ -484,62 +494,28 @@ function welcomeCard(cliVersion) {
|
|
|
484
494
|
const green = s => !useColor ? s : `\x1b[32m${s}\x1b[0m`;
|
|
485
495
|
|
|
486
496
|
const { rows: wm, w: wmW } = padRows(CARD_WORDMARK);
|
|
487
|
-
const
|
|
488
|
-
const
|
|
489
|
-
const innerW = margin + mkW + gutter + wmW + margin;
|
|
490
|
-
const cardW = innerW + 2;
|
|
491
|
-
|
|
492
|
-
if (!isTTY || noColor) { compactCard(cliVersion, false); return; }
|
|
493
|
-
if (!utf8 || width < 87) { compactCard(cliVersion, useColor); return; }
|
|
494
|
-
if (!truecolor || width < cardW + 2) { wordmarkCard({ orange, dim, green, wm }); return; }
|
|
495
|
-
|
|
497
|
+
const margin = 2;
|
|
498
|
+
const innerW = margin + wmW + margin;
|
|
496
499
|
const boxRow = (colored, vis) => frame('│') + colored + rep(' ', Math.max(0, innerW - vis)) + frame('│');
|
|
500
|
+
|
|
497
501
|
writeln();
|
|
498
502
|
const title = ' Welcome to Swytchcode ';
|
|
499
503
|
writeln(frame('┌─' + title + rep('─', innerW - 1 - vlen(title)) + '┐'));
|
|
500
504
|
writeln(boxRow('', 0));
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
const wmOff = Math.max(0, Math.floor((mk.length - wm.length) / 2));
|
|
504
|
-
for (let i = 0; i < nRows; i++) {
|
|
505
|
-
const left = i < mk.length ? orange(mk[i]) : rep(' ', mkW);
|
|
506
|
-
const wi = i - wmOff;
|
|
507
|
-
const right = (wi >= 0 && wi < wm.length) ? orange(wm[wi]) : rep(' ', wmW);
|
|
508
|
-
writeln(boxRow(rep(' ', margin) + left + rep(' ', gutter) + right, margin + mkW + gutter + wmW));
|
|
509
|
-
}
|
|
505
|
+
for (const row of wm) writeln(boxRow(rep(' ', margin) + orange(row), margin + wmW));
|
|
506
|
+
writeln(boxRow(rep(' ', margin) + dim('v' + cliVersion), margin + 1 + vlen(cliVersion)));
|
|
510
507
|
writeln(boxRow('', 0));
|
|
511
|
-
// Tagline centered across the inner width.
|
|
512
508
|
const lead = margin + Math.floor((innerW - 2 * margin - vlen(CARD_TAGLINE)) / 2);
|
|
513
509
|
writeln(boxRow(rep(' ', lead) + dim(CARD_TAGLINE), lead + vlen(CARD_TAGLINE)));
|
|
514
510
|
writeln(boxRow('', 0));
|
|
511
|
+
writeDescRows(boxRow, orange, innerW - 2 * margin, margin);
|
|
512
|
+
writeln(boxRow('', 0));
|
|
515
513
|
writeln(frame('└' + rep('─', innerW) + '┘'));
|
|
516
514
|
cardBelow({ orange, dim, green });
|
|
517
515
|
}
|
|
518
516
|
|
|
519
|
-
function compactCard(cliVersion, useColor) {
|
|
520
|
-
const orange = s => useColor ? `\x1b[38;5;214m${s}\x1b[0m` : s;
|
|
521
|
-
const dim = s => useColor ? `\x1b[2m${s}\x1b[0m` : s;
|
|
522
|
-
writeln();
|
|
523
|
-
writeln(` ${orange('swytchcode')} ${dim('v' + cliVersion)}`);
|
|
524
|
-
writeln(` ${CARD_TAGLINE}`);
|
|
525
|
-
writeln();
|
|
526
|
-
writeln(` ${dim('docs')} docs.swytchcode.com`);
|
|
527
|
-
writeln(` ${dim('chat')} discord.gg/zuSXSv5GWs`);
|
|
528
|
-
writeln();
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
function wordmarkCard({ orange, dim, green, wm }) {
|
|
532
|
-
writeln();
|
|
533
|
-
for (const row of wm) writeln(` ${orange(row)}`);
|
|
534
|
-
writeln(`\n ${dim(CARD_TAGLINE)}`);
|
|
535
|
-
cardBelow({ orange, dim, green });
|
|
536
|
-
}
|
|
537
|
-
|
|
538
517
|
// cardBelow prints the description, sign-in line, cwd, and get-started hints.
|
|
539
518
|
function cardBelow({ orange, dim, green }) {
|
|
540
|
-
writeln();
|
|
541
|
-
writeln('Swytchcode sits between your agent and any API — auth, retries, idempotency, and schema');
|
|
542
|
-
writeln(`validation, handled. Run a command below, or ${orange('swytchcode --help')} for the full list.`);
|
|
543
519
|
writeln();
|
|
544
520
|
writeln(`${orange('●')} ${dim('not signed in — run ')}${orange('swytchcode login')}`);
|
|
545
521
|
const cwd = process.cwd().replace(/\\/g, '/');
|
|
@@ -575,55 +551,102 @@ async function runOnboarding() {
|
|
|
575
551
|
|
|
576
552
|
welcomeCard(cliVersion);
|
|
577
553
|
|
|
554
|
+
// Divider between the welcome card/info and the interactive walkthrough,
|
|
555
|
+
// matching the rules drawn between steps below.
|
|
556
|
+
writeln();
|
|
557
|
+
writeln(' ' + '─'.repeat(45));
|
|
558
|
+
writeln();
|
|
559
|
+
|
|
578
560
|
// ── Step 1: Demo ──────────────────────────────────────────────────────────
|
|
579
|
-
writeln(`${c.bold} Step 1 of 4 —
|
|
580
|
-
writeln(`${c.dim} Run a
|
|
561
|
+
writeln(`${c.bold} Step 1 of 4 — Run a live call${c.reset}`);
|
|
562
|
+
writeln(`${c.dim} Run a real API call through Swytchcode.${c.reset}`);
|
|
581
563
|
writeln();
|
|
582
564
|
await runDemoSelection();
|
|
583
565
|
await runOnboardingSteps2to4();
|
|
584
566
|
}
|
|
585
567
|
|
|
568
|
+
// isGloballyInstalled reports whether a real, persistent global `swytchcode`
|
|
569
|
+
// exists on PATH — as opposed to the transient npx cache binary this script is
|
|
570
|
+
// currently running from. Used to skip the "install globally" onboarding step
|
|
571
|
+
// when it's already installed.
|
|
572
|
+
function isGloballyInstalled() {
|
|
573
|
+
try {
|
|
574
|
+
const probe = process.platform === 'win32' ? 'where swytchcode' : 'command -v swytchcode';
|
|
575
|
+
const out = execSync(probe, { stdio: ['ignore', 'pipe', 'ignore'], encoding: 'utf8' }).trim();
|
|
576
|
+
if (!out) return false;
|
|
577
|
+
// Any resolved path that isn't inside the npx cache means a global install exists.
|
|
578
|
+
return out.split(/\r?\n/).some(line => line && !line.toLowerCase().includes('_npx'));
|
|
579
|
+
} catch (_) {
|
|
580
|
+
return false; // not found on PATH
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// authStatus reports whether the user is already authenticated, so onboarding
|
|
585
|
+
// can skip the login prompt. Priority: SWYTCHCODE_TOKEN env var, then a
|
|
586
|
+
// SWYTCHCODE_TOKEN in ./.env, then a valid (unexpired) user session in
|
|
587
|
+
// ~/.swytchcode/auth.json.
|
|
588
|
+
function authStatus() {
|
|
589
|
+
if ((process.env.SWYTCHCODE_TOKEN || '').trim()) {
|
|
590
|
+
return { authed: true, label: 'authenticated via SWYTCHCODE_TOKEN' };
|
|
591
|
+
}
|
|
592
|
+
try {
|
|
593
|
+
const envPath = path.join(process.cwd(), '.env');
|
|
594
|
+
if (fs.existsSync(envPath)) {
|
|
595
|
+
const m = fs.readFileSync(envPath, 'utf8').match(/^\s*SWYTCHCODE_TOKEN\s*=\s*["']?([^"'\r\n#]+)/m);
|
|
596
|
+
if (m && m[1].trim()) return { authed: true, label: 'authenticated via .env token' };
|
|
597
|
+
}
|
|
598
|
+
} catch (_) {}
|
|
599
|
+
try {
|
|
600
|
+
const authPath = path.join(require('os').homedir(), '.swytchcode', 'auth.json');
|
|
601
|
+
const s = JSON.parse(fs.readFileSync(authPath, 'utf8'));
|
|
602
|
+
if (s && s.access_token && typeof s.expires_at === 'number' && Date.now() < s.expires_at * 1000) {
|
|
603
|
+
return { authed: true, label: s.email ? ('signed in as ' + s.email) : 'signed in' };
|
|
604
|
+
}
|
|
605
|
+
} catch (_) {}
|
|
606
|
+
return { authed: false, label: '' };
|
|
607
|
+
}
|
|
608
|
+
|
|
586
609
|
async function runOnboardingSteps2to4() {
|
|
587
610
|
// ── Step 2: Install ───────────────────────────────────────────────────────
|
|
588
611
|
writeln();
|
|
589
612
|
writeln(' ' + '─'.repeat(45));
|
|
590
613
|
writeln();
|
|
591
614
|
writeln(`${c.bold} Step 2 of 4 — Install Swytchcode${c.reset}`);
|
|
592
|
-
writeln(`${c.dim} Install once, use everywhere. Adds the ${c.reset}${c.cyan}swytchcode${c.reset}${c.dim} command to your terminal.${c.reset}`);
|
|
593
|
-
writeln();
|
|
594
|
-
writeln(` ${c.dim}Installing into: ${process.cwd()}${c.reset}`);
|
|
595
|
-
writeln(` ${c.dim}$ npm install swytchcode${c.reset}`);
|
|
596
|
-
writeln();
|
|
597
615
|
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
{
|
|
601
|
-
|
|
616
|
+
if (isGloballyInstalled()) {
|
|
617
|
+
// Already installed — don't re-offer the install step.
|
|
618
|
+
writeln(` ${c.green}›${c.reset} ${c.dim}swytchcode is already installed globally — skipping.${c.reset}`);
|
|
619
|
+
} else {
|
|
620
|
+
writeln(`${c.dim} Install the ${c.reset}${c.orange}swytchcode${c.reset}${c.dim} command globally.${c.reset}`);
|
|
621
|
+
writeln();
|
|
622
|
+
writeln(` ${c.dim}$ npm install -g swytchcode${c.reset}`);
|
|
623
|
+
writeln();
|
|
602
624
|
|
|
603
|
-
|
|
625
|
+
const installChoice = await menuArrow('Install globally?', [
|
|
626
|
+
{ label: 'Yes, install globally', value: 'yes' },
|
|
627
|
+
{ label: 'Skip for now', value: 'no' },
|
|
628
|
+
]);
|
|
604
629
|
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
630
|
+
writeln();
|
|
631
|
+
|
|
632
|
+
if (installChoice === 'no') {
|
|
633
|
+
writeln(` ${c.dim}Skipped. Install manually with:${c.reset}`);
|
|
634
|
+
writeln(` ${c.orange}npm install -g swytchcode${c.reset}`);
|
|
635
|
+
} else {
|
|
636
|
+
const installSpin = spinner('Running npm install -g swytchcode ...');
|
|
637
|
+
try {
|
|
638
|
+
execSync('npm install -g swytchcode', { stdio: 'pipe' });
|
|
639
|
+
installSpin.stop('installed');
|
|
640
|
+
} catch (e) {
|
|
641
|
+
if (e.signal === 'SIGINT') {
|
|
642
|
+
installSpin.stop('Cancelled');
|
|
643
|
+
try { process.stdin.setRawMode(false); } catch (_) {}
|
|
644
|
+
process.stdout.write('\x1b[?25h');
|
|
645
|
+
process.exit(130);
|
|
646
|
+
}
|
|
647
|
+
installSpin.fail('Installation failed.');
|
|
620
648
|
}
|
|
621
|
-
installSpin.fail('Installation failed.');
|
|
622
649
|
}
|
|
623
|
-
|
|
624
|
-
writeln();
|
|
625
|
-
writeln(` ${c.dim}To make it available globally, run:${c.reset}`);
|
|
626
|
-
writeln(` ${c.bold}${c.green}npm install -g swytchcode${c.reset}`);
|
|
627
650
|
}
|
|
628
651
|
|
|
629
652
|
// ── Step 3: Account sync ──────────────────────────────────────────────────
|
|
@@ -631,41 +654,48 @@ async function runOnboardingSteps2to4() {
|
|
|
631
654
|
writeln(' ' + '─'.repeat(45));
|
|
632
655
|
writeln();
|
|
633
656
|
writeln(`${c.bold} Step 3 of 4 — Connect your account${c.reset}`);
|
|
634
|
-
writeln(`${c.dim} See your usage analytics, execution history, and saved projects.${c.reset}`);
|
|
635
|
-
writeln();
|
|
636
657
|
|
|
637
|
-
const
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
{ label
|
|
641
|
-
|
|
658
|
+
const auth = authStatus();
|
|
659
|
+
if (auth.authed) {
|
|
660
|
+
// Already authenticated — never re-prompt for login.
|
|
661
|
+
writeln(` ${c.green}›${c.reset} ${c.dim}Already ${auth.label} — skipping.${c.reset}`);
|
|
662
|
+
} else {
|
|
663
|
+
writeln(`${c.dim} See your usage analytics, execution history, and saved projects.${c.reset}`);
|
|
664
|
+
writeln();
|
|
642
665
|
|
|
643
|
-
|
|
666
|
+
const authChoice = await menuArrow('Do you already have an account?', [
|
|
667
|
+
{ label: 'Login to my account', value: 'login' },
|
|
668
|
+
{ label: 'Create a new account', value: 'register' },
|
|
669
|
+
{ label: 'Continue anonymously', value: 'anon' },
|
|
670
|
+
]);
|
|
644
671
|
|
|
645
|
-
if (authChoice === 'login') {
|
|
646
|
-
writeln(` ${c.dim}Opening browser for login ...${c.reset}`);
|
|
647
|
-
writeln();
|
|
648
|
-
const result = spawnBin(binPath, ['login']);
|
|
649
|
-
if (result.error || result.status !== 0) {
|
|
650
|
-
writeln(` ${c.yellow}Login window closed or failed.${c.reset}`);
|
|
651
|
-
writeln(` ${c.dim}You can log in later with:${c.reset} ${c.cyan}swytchcode login${c.reset}`);
|
|
652
|
-
} else {
|
|
653
|
-
writeln(` ${c.green}›${c.reset} logged in`);
|
|
654
|
-
}
|
|
655
|
-
} else if (authChoice === 'register') {
|
|
656
|
-
writeln(` ${c.dim}Opening browser to create your account ...${c.reset}`);
|
|
657
672
|
writeln();
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
writeln(
|
|
662
|
-
|
|
673
|
+
|
|
674
|
+
if (authChoice === 'login') {
|
|
675
|
+
writeln(` ${c.dim}Opening browser for login ...${c.reset}`);
|
|
676
|
+
writeln();
|
|
677
|
+
const result = spawnBin(binPath, ['login']);
|
|
678
|
+
if (result.error || result.status !== 0) {
|
|
679
|
+
writeln(` ${c.yellow}Login window closed or failed.${c.reset}`);
|
|
680
|
+
writeln(` ${c.dim}You can log in later with:${c.reset} ${c.orange}swytchcode login${c.reset}`);
|
|
681
|
+
} else {
|
|
682
|
+
writeln(` ${c.green}›${c.reset} logged in`);
|
|
683
|
+
}
|
|
684
|
+
} else if (authChoice === 'register') {
|
|
685
|
+
writeln(` ${c.dim}Opening browser to create your account ...${c.reset}`);
|
|
686
|
+
writeln();
|
|
687
|
+
// login command handles both login and registration via the browser flow
|
|
688
|
+
const result = spawnBin(binPath, ['login']);
|
|
689
|
+
if (result.error || result.status !== 0) {
|
|
690
|
+
writeln(` ${c.yellow}Browser window closed or failed.${c.reset}`);
|
|
691
|
+
writeln(` ${c.dim}Create your account at:${c.reset} ${c.orange}https://swytchcode.com/signup${c.reset}`);
|
|
692
|
+
} else {
|
|
693
|
+
writeln(` ${c.green}›${c.reset} account connected`);
|
|
694
|
+
}
|
|
663
695
|
} else {
|
|
664
|
-
writeln(` ${c.
|
|
696
|
+
writeln(` ${c.dim}No problem — you can connect your account any time with:${c.reset}`);
|
|
697
|
+
writeln(` ${c.orange}swytchcode login${c.reset}`);
|
|
665
698
|
}
|
|
666
|
-
} else {
|
|
667
|
-
writeln(` ${c.dim}No problem — you can connect your account any time with:${c.reset}`);
|
|
668
|
-
writeln(` ${c.cyan}swytchcode login${c.reset}`);
|
|
669
699
|
}
|
|
670
700
|
|
|
671
701
|
// ── Step 4: Next action ───────────────────────────────────────────────────
|
|
@@ -701,11 +731,11 @@ async function runOnboardingSteps2to4() {
|
|
|
701
731
|
writeln(` ${c.bold}You're all set.${c.reset}`);
|
|
702
732
|
writeln();
|
|
703
733
|
writeln(` Quick reference:`);
|
|
704
|
-
writeln(` ${c.
|
|
705
|
-
writeln(` ${c.
|
|
706
|
-
writeln(` ${c.
|
|
707
|
-
writeln(` ${c.
|
|
708
|
-
writeln(` ${c.
|
|
734
|
+
writeln(` ${c.orange}swytchcode init${c.reset} ${c.dim}Set up a new project${c.reset}`);
|
|
735
|
+
writeln(` ${c.orange}swytchcode examples${c.reset} ${c.dim}Browse example integrations${c.reset}`);
|
|
736
|
+
writeln(` ${c.orange}swytchcode get <api>${c.reset} ${c.dim}Install an API integration${c.reset}`);
|
|
737
|
+
writeln(` ${c.orange}swytchcode exec <id>${c.reset} ${c.dim}Execute an API method${c.reset}`);
|
|
738
|
+
writeln(` ${c.orange}swytchcode --help${c.reset} ${c.dim}Full command reference${c.reset}`);
|
|
709
739
|
}
|
|
710
740
|
|
|
711
741
|
writeln();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "swytchcode",
|
|
3
|
-
"version": "2.14.0-beta.
|
|
3
|
+
"version": "2.14.0-beta.3",
|
|
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.14.0-beta.
|
|
32
|
-
"swytchcode-cli-darwin-x64": "2.14.0-beta.
|
|
33
|
-
"swytchcode-cli-linux-arm64": "2.14.0-beta.
|
|
34
|
-
"swytchcode-cli-linux-x64": "2.14.0-beta.
|
|
35
|
-
"swytchcode-cli-win32-arm64": "2.14.0-beta.
|
|
36
|
-
"swytchcode-cli-win32-x64": "2.14.0-beta.
|
|
31
|
+
"swytchcode-cli-darwin-arm64": "2.14.0-beta.3",
|
|
32
|
+
"swytchcode-cli-darwin-x64": "2.14.0-beta.3",
|
|
33
|
+
"swytchcode-cli-linux-arm64": "2.14.0-beta.3",
|
|
34
|
+
"swytchcode-cli-linux-x64": "2.14.0-beta.3",
|
|
35
|
+
"swytchcode-cli-win32-arm64": "2.14.0-beta.3",
|
|
36
|
+
"swytchcode-cli-win32-x64": "2.14.0-beta.3"
|
|
37
37
|
}
|
|
38
38
|
}
|