swytchcode 2.3.22 → 2.5.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.
- package/bin/swytchcode.js +450 -34
- package/package.json +8 -8
package/bin/swytchcode.js
CHANGED
|
@@ -1,37 +1,225 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
const { spawnSync } = require('child_process');
|
|
5
|
-
const fs
|
|
4
|
+
const { spawnSync, execSync } = require('child_process');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const rl = require('readline');
|
|
8
|
+
const https = require('https');
|
|
9
|
+
const http = require('http');
|
|
6
10
|
|
|
7
|
-
//
|
|
8
|
-
|
|
9
|
-
|
|
11
|
+
// ─── Colours ────────────────────────────────────────────────────────────────
|
|
12
|
+
const c = {
|
|
13
|
+
reset: '\x1b[0m',
|
|
14
|
+
bold: '\x1b[1m',
|
|
15
|
+
dim: '\x1b[2m',
|
|
16
|
+
green: '\x1b[32m',
|
|
17
|
+
yellow: '\x1b[33m',
|
|
18
|
+
cyan: '\x1b[36m',
|
|
19
|
+
white: '\x1b[37m',
|
|
20
|
+
gray: '\x1b[90m',
|
|
21
|
+
bgGreen: '\x1b[42m',
|
|
22
|
+
red: '\x1b[31m',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
26
|
+
function write(msg) { process.stdout.write(msg); }
|
|
27
|
+
function writeln(msg = '') { process.stdout.write(msg + '\n'); }
|
|
28
|
+
function err(msg) { process.stderr.write(msg + '\n'); }
|
|
29
|
+
|
|
30
|
+
function box(lines, colour = c.cyan) {
|
|
31
|
+
const width = Math.max(...lines.map(l => stripAnsi(l).length)) + 4;
|
|
32
|
+
const bar = colour + '─'.repeat(width) + c.reset;
|
|
33
|
+
writeln(bar);
|
|
34
|
+
for (const line of lines) {
|
|
35
|
+
const pad = width - 2 - stripAnsi(line).length;
|
|
36
|
+
writeln(colour + ' ' + c.reset + line + ' '.repeat(pad) + colour + ' ' + c.reset);
|
|
37
|
+
}
|
|
38
|
+
writeln(bar);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function stripAnsi(str) {
|
|
42
|
+
return str.replace(/\x1b\[[0-9;]*m/g, '');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function ask(prompt) {
|
|
46
|
+
return new Promise(resolve => {
|
|
47
|
+
const iface = rl.createInterface({ input: process.stdin, output: process.stdout });
|
|
48
|
+
iface.question(prompt, answer => { iface.close(); resolve(answer.trim()); });
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function menuArrow(prompt, options) {
|
|
53
|
+
// options: [{ label, value }]
|
|
54
|
+
return new Promise(async resolve => {
|
|
55
|
+
if (!process.stdin.isTTY) {
|
|
56
|
+
// TTY fallback to number input
|
|
57
|
+
writeln();
|
|
58
|
+
writeln(c.bold + prompt + c.reset);
|
|
59
|
+
writeln();
|
|
60
|
+
options.forEach((o, i) => {
|
|
61
|
+
writeln(` ${c.cyan}${i + 1}${c.reset} ${stripAnsi(o.label)}`);
|
|
62
|
+
});
|
|
63
|
+
writeln();
|
|
64
|
+
while (true) {
|
|
65
|
+
const raw = await ask(`${c.gray}Enter number [1-${options.length}]: ${c.reset}`);
|
|
66
|
+
const n = parseInt(raw, 10);
|
|
67
|
+
if (n >= 1 && n <= options.length) {
|
|
68
|
+
resolve(options[n - 1].value);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
writeln(`${c.red} Invalid choice. Please enter a number between 1 and ${options.length}.${c.reset}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// TTY interactive mode with arrow keys
|
|
76
|
+
let selectedIndex = 0;
|
|
77
|
+
|
|
78
|
+
// Hide cursor
|
|
79
|
+
write('\x1b[?25l');
|
|
80
|
+
|
|
81
|
+
function render() {
|
|
82
|
+
writeln();
|
|
83
|
+
writeln(c.bold + prompt + c.reset);
|
|
84
|
+
writeln();
|
|
85
|
+
options.forEach((o, i) => {
|
|
86
|
+
if (i === selectedIndex) {
|
|
87
|
+
writeln(` ${c.cyan}❯${c.reset} ${c.bold}${c.green}${stripAnsi(o.label)}${c.reset}`);
|
|
88
|
+
} else {
|
|
89
|
+
writeln(` ${c.dim}${stripAnsi(o.label)}${c.reset}`);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
writeln();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function clean() {
|
|
96
|
+
const lines = options.length + 4;
|
|
97
|
+
rl.moveCursor(process.stdout, 0, -lines);
|
|
98
|
+
rl.clearScreenDown(process.stdout);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
render();
|
|
102
|
+
|
|
103
|
+
const onKeypress = (char, key) => {
|
|
104
|
+
if (key) {
|
|
105
|
+
if (key.name === 'up') {
|
|
106
|
+
selectedIndex = (selectedIndex - 1 + options.length) % options.length;
|
|
107
|
+
clean();
|
|
108
|
+
render();
|
|
109
|
+
} else if (key.name === 'down') {
|
|
110
|
+
selectedIndex = (selectedIndex + 1) % options.length;
|
|
111
|
+
clean();
|
|
112
|
+
render();
|
|
113
|
+
} else if (key.name === 'return' || key.name === 'enter') {
|
|
114
|
+
cleanup();
|
|
115
|
+
resolve(options[selectedIndex].value);
|
|
116
|
+
} else if (key.ctrl && key.name === 'c') {
|
|
117
|
+
cleanup();
|
|
118
|
+
process.exit(130);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
rl.emitKeypressEvents(process.stdin);
|
|
124
|
+
process.stdin.setRawMode(true);
|
|
125
|
+
process.stdin.resume();
|
|
126
|
+
process.stdin.on('keypress', onKeypress);
|
|
127
|
+
|
|
128
|
+
function cleanup() {
|
|
129
|
+
// Show cursor
|
|
130
|
+
write('\x1b[?25h');
|
|
131
|
+
process.stdin.removeListener('keypress', onKeypress);
|
|
132
|
+
try {
|
|
133
|
+
process.stdin.setRawMode(false);
|
|
134
|
+
} catch (_) {}
|
|
135
|
+
process.stdin.pause();
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function spinner(msg) {
|
|
141
|
+
const frames = ['⠋','⠙','⠹','⠸','⠼','⠴','⠦','⠧','⠇','⠏'];
|
|
142
|
+
let i = 0;
|
|
143
|
+
const iv = setInterval(() => {
|
|
144
|
+
process.stdout.write(`\r${c.cyan}${frames[i++ % frames.length]}${c.reset} ${msg}`);
|
|
145
|
+
}, 80);
|
|
146
|
+
return {
|
|
147
|
+
stop(doneMsg) {
|
|
148
|
+
clearInterval(iv);
|
|
149
|
+
process.stdout.write(`\r${c.green}✔${c.reset} ${doneMsg}\n`);
|
|
150
|
+
},
|
|
151
|
+
fail(failMsg) {
|
|
152
|
+
clearInterval(iv);
|
|
153
|
+
process.stdout.write(`\r${c.red}✖${c.reset} ${failMsg}\n`);
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function apiPost(urlStr, body) {
|
|
159
|
+
return new Promise((resolve, reject) => {
|
|
160
|
+
const payload = JSON.stringify(body);
|
|
161
|
+
const parsed = new URL(urlStr);
|
|
162
|
+
const lib = parsed.protocol === 'https:' ? https : http;
|
|
163
|
+
const req = lib.request({
|
|
164
|
+
hostname: parsed.hostname,
|
|
165
|
+
port: parsed.port || (parsed.protocol === 'https:' ? 443 : 80),
|
|
166
|
+
path: parsed.pathname + parsed.search,
|
|
167
|
+
method: 'POST',
|
|
168
|
+
headers: {
|
|
169
|
+
'Content-Type': 'application/json',
|
|
170
|
+
'Content-Length': Buffer.byteLength(payload),
|
|
171
|
+
'User-Agent': 'swytchcode-npx-onboarding/1.0',
|
|
172
|
+
},
|
|
173
|
+
}, res => {
|
|
174
|
+
let data = '';
|
|
175
|
+
res.on('data', d => { data += d; });
|
|
176
|
+
res.on('end', () => {
|
|
177
|
+
try { resolve({ status: res.statusCode, body: JSON.parse(data) }); }
|
|
178
|
+
catch { resolve({ status: res.statusCode, body: data }); }
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
req.on('error', reject);
|
|
182
|
+
req.setTimeout(15000, () => { req.destroy(new Error('Request timed out')); });
|
|
183
|
+
req.write(payload);
|
|
184
|
+
req.end();
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ─── Binary resolution (shared by normal pass-through AND onboarding) ────────
|
|
10
189
|
function spawnBin(binPath, args) {
|
|
11
190
|
if (process.stdin.isTTY) {
|
|
12
191
|
return spawnSync(binPath, args, { stdio: 'inherit' });
|
|
13
192
|
}
|
|
14
193
|
let stdinData;
|
|
15
|
-
try {
|
|
16
|
-
|
|
17
|
-
} catch (_) {
|
|
18
|
-
stdinData = Buffer.alloc(0);
|
|
19
|
-
}
|
|
194
|
+
try { stdinData = fs.readFileSync(0); }
|
|
195
|
+
catch (_) { stdinData = Buffer.alloc(0); }
|
|
20
196
|
return spawnSync(binPath, args, {
|
|
21
197
|
stdio: ['pipe', 'inherit', 'inherit'],
|
|
22
198
|
input: stdinData,
|
|
23
199
|
});
|
|
24
200
|
}
|
|
25
201
|
|
|
26
|
-
|
|
202
|
+
function exitCode(result) {
|
|
203
|
+
if (result.status !== null && result.status !== undefined) return result.status;
|
|
204
|
+
const SIGNALS = {
|
|
205
|
+
SIGHUP:1,SIGINT:2,SIGQUIT:3,SIGILL:4,SIGTRAP:5,SIGABRT:6,
|
|
206
|
+
SIGBUS:7,SIGFPE:8,SIGKILL:9,SIGUSR1:10,SIGSEGV:11,SIGUSR2:12,
|
|
207
|
+
SIGPIPE:13,SIGALRM:14,SIGTERM:15,
|
|
208
|
+
};
|
|
209
|
+
const sig = result.signal;
|
|
210
|
+
if (sig && SIGNALS[sig] !== undefined) return 128 + SIGNALS[sig];
|
|
211
|
+
return 1;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Explicit override: SWYTCHCODE_BIN=/path/to/binary bypasses platform resolution.
|
|
27
215
|
const explicitBin = (process.env.SWYTCHCODE_BIN || '').trim();
|
|
28
216
|
if (explicitBin) {
|
|
29
217
|
const result = spawnBin(explicitBin, process.argv.slice(2));
|
|
30
218
|
if (result.error) {
|
|
31
|
-
|
|
219
|
+
err(`swytchcode: failed to launch SWYTCHCODE_BIN="${explicitBin}": ${result.error.message}`);
|
|
32
220
|
process.exit(1);
|
|
33
221
|
}
|
|
34
|
-
process.exit(result
|
|
222
|
+
process.exit(exitCode(result));
|
|
35
223
|
}
|
|
36
224
|
|
|
37
225
|
const PLATFORM_PACKAGES = {
|
|
@@ -44,53 +232,281 @@ const PLATFORM_PACKAGES = {
|
|
|
44
232
|
};
|
|
45
233
|
|
|
46
234
|
const platform = process.platform;
|
|
47
|
-
const arch
|
|
48
|
-
const key
|
|
49
|
-
const pkg
|
|
235
|
+
const arch = process.arch;
|
|
236
|
+
const key = `${platform}-${arch}`;
|
|
237
|
+
const pkg = PLATFORM_PACKAGES[key];
|
|
50
238
|
|
|
51
239
|
if (!pkg) {
|
|
52
|
-
|
|
240
|
+
err(
|
|
53
241
|
`swytchcode: unsupported platform "${key}".\n` +
|
|
54
242
|
`Supported: ${Object.keys(PLATFORM_PACKAGES).join(', ')}\n` +
|
|
55
|
-
`Install via curl instead: https://cli.swytchcode.com
|
|
243
|
+
`Install via curl instead: https://cli.swytchcode.com`
|
|
56
244
|
);
|
|
57
245
|
process.exit(1);
|
|
58
246
|
}
|
|
59
247
|
|
|
60
248
|
const isWindows = platform === 'win32';
|
|
61
|
-
const binName
|
|
62
|
-
|
|
249
|
+
const binName = isWindows ? 'swytchcode.exe' : 'swytchcode';
|
|
63
250
|
let binPath;
|
|
64
251
|
try {
|
|
65
252
|
binPath = require.resolve(`${pkg}/bin/${binName}`);
|
|
66
253
|
} catch (_) {
|
|
67
|
-
|
|
254
|
+
err(
|
|
68
255
|
`swytchcode: could not find the binary for "${key}".\n` +
|
|
69
|
-
`Try reinstalling: npm install -g swytchcode
|
|
256
|
+
`Try reinstalling: npm install -g swytchcode`
|
|
70
257
|
);
|
|
71
258
|
process.exit(1);
|
|
72
259
|
}
|
|
73
260
|
|
|
74
|
-
// Detect npx
|
|
75
|
-
// older npm puts the binary in a _npx temp dir, and even older npm names npx in npm_execpath.
|
|
261
|
+
// Detect npx
|
|
76
262
|
const isNpx =
|
|
77
263
|
(process.env.npm_command || '') === 'exec' ||
|
|
78
264
|
__filename.includes('_npx') ||
|
|
79
265
|
binPath.includes('_npx') ||
|
|
80
266
|
(process.env.npm_execpath || '').includes('npx');
|
|
81
267
|
|
|
82
|
-
const
|
|
268
|
+
const userArgs = process.argv.slice(2);
|
|
83
269
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
270
|
+
// Legacy support: npx swytchcode <canonical_id> → npx swytchcode exec <canonical_id>
|
|
271
|
+
if (userArgs.length > 0 && /^[\w]+\.[\w.]+$/.test(userArgs[0]) && !userArgs[0].startsWith('-')) {
|
|
272
|
+
userArgs.unshift('exec');
|
|
87
273
|
}
|
|
88
274
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
275
|
+
// ─── Normal pass-through (not npx, or npx with args) ────────────────────────
|
|
276
|
+
if (!isNpx || userArgs.length > 0) {
|
|
277
|
+
const result = spawnBin(binPath, userArgs);
|
|
278
|
+
if (result.error) {
|
|
279
|
+
err(`swytchcode: failed to launch binary: ${result.error.message}`);
|
|
280
|
+
process.exit(1);
|
|
281
|
+
}
|
|
282
|
+
process.exit(exitCode(result));
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// ─── NPX ONBOARDING FLOW ─────────────────────────────────────────────────────
|
|
286
|
+
// Only reached when: isNpx === true AND no args passed (bare `npx swytchcode`)
|
|
287
|
+
|
|
288
|
+
const API_BASE = 'https://api-v2.swytchcode.com/v2/cli';
|
|
289
|
+
|
|
290
|
+
// Helper to perform HTTP GET (declared inside so it doesn't conflict)
|
|
291
|
+
function apiGet(urlStr) {
|
|
292
|
+
return new Promise((resolve, reject) => {
|
|
293
|
+
const parsed = new URL(urlStr);
|
|
294
|
+
const lib = parsed.protocol === 'https:' ? https : http;
|
|
295
|
+
const req = lib.request({
|
|
296
|
+
hostname: parsed.hostname,
|
|
297
|
+
port: parsed.port || (parsed.protocol === 'https:' ? 443 : 80),
|
|
298
|
+
path: parsed.pathname + parsed.search,
|
|
299
|
+
method: 'GET',
|
|
300
|
+
headers: { 'User-Agent': 'swytchcode-npx-onboarding/1.0' },
|
|
301
|
+
}, res => {
|
|
302
|
+
let data = '';
|
|
303
|
+
res.on('data', d => { data += d; });
|
|
304
|
+
res.on('end', () => {
|
|
305
|
+
try { resolve({ status: res.statusCode, body: JSON.parse(data) }); }
|
|
306
|
+
catch { resolve({ status: res.statusCode, body: data }); }
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
req.on('error', reject);
|
|
310
|
+
req.setTimeout(10000, () => { req.destroy(new Error('Request timed out')); });
|
|
311
|
+
req.end();
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
async function runOnboarding() {
|
|
315
|
+
let cliVersion = '0.0.0';
|
|
316
|
+
try {
|
|
317
|
+
const verResult = spawnSync(binPath, ['--version'], { encoding: 'utf8' });
|
|
318
|
+
const semverMatch = verResult.stdout && verResult.stdout.match(/[\d]+\.[\d]+\.[\d]+/);
|
|
319
|
+
if (semverMatch) {
|
|
320
|
+
cliVersion = semverMatch[0];
|
|
321
|
+
} else if (verResult.stdout && verResult.stdout.includes('dev')) {
|
|
322
|
+
cliVersion = 'dev';
|
|
323
|
+
} else {
|
|
324
|
+
const pkgJson = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'), 'utf8'));
|
|
325
|
+
cliVersion = pkgJson.version || '0.0.0';
|
|
326
|
+
}
|
|
327
|
+
} catch (_) {}
|
|
328
|
+
|
|
329
|
+
// Clear screen for a clean experience
|
|
330
|
+
process.stdout.write('\x1Bc');
|
|
331
|
+
|
|
332
|
+
writeln();
|
|
333
|
+
const vPad = ' '.repeat(Math.max(0, 34 - cliVersion.length));
|
|
334
|
+
writeln(` ${c.yellow}==========================================${c.reset}`);
|
|
335
|
+
writeln(` ${c.yellow}|${c.reset} ${c.yellow}⚡ ${c.bold}Welcome to Swytchcode${c.reset} ${c.yellow}|${c.reset}`);
|
|
336
|
+
writeln(` ${c.yellow}|${c.reset} ${c.dim}v${cliVersion}${c.reset}${vPad}${c.yellow}|${c.reset}`);
|
|
337
|
+
writeln(` ${c.yellow}|${c.reset} ${c.dim}Execution authority layer${c.reset} ${c.yellow}|${c.reset}`);
|
|
338
|
+
writeln(` ${c.yellow}==========================================${c.reset}`);
|
|
339
|
+
writeln();
|
|
340
|
+
|
|
341
|
+
// ── Step 1: Demo ──────────────────────────────────────────────────────────
|
|
342
|
+
writeln(`${c.bold} Step 1 of 4 — See it in action${c.reset}`);
|
|
343
|
+
writeln(`${c.dim} Run a live API call right now, no setup required.${c.reset}`);
|
|
344
|
+
writeln();
|
|
345
|
+
|
|
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
|
+
|
|
384
|
+
// ── Step 2: Install ───────────────────────────────────────────────────────
|
|
385
|
+
writeln();
|
|
386
|
+
writeln(' ' + '─'.repeat(45));
|
|
387
|
+
writeln();
|
|
388
|
+
writeln(`${c.bold} Step 2 of 4 — Install Swytchcode${c.reset}`);
|
|
389
|
+
writeln(`${c.dim} Install once, use everywhere. Adds the ${c.reset}${c.cyan}swytchcode${c.reset}${c.dim} command to your terminal.${c.reset}`);
|
|
390
|
+
writeln();
|
|
391
|
+
writeln(` ${c.dim}Installing into: ${process.cwd()}${c.reset}`);
|
|
392
|
+
writeln(` ${c.dim}$ npm install swytchcode${c.reset}`);
|
|
393
|
+
writeln();
|
|
394
|
+
|
|
395
|
+
const installChoice = await menuArrow('Install here?', [
|
|
396
|
+
{ label: 'Yes, install here', value: 'yes' },
|
|
397
|
+
{ label: 'Skip for now', value: 'no' },
|
|
398
|
+
]);
|
|
399
|
+
|
|
400
|
+
writeln();
|
|
401
|
+
|
|
402
|
+
if (installChoice === 'no') {
|
|
403
|
+
writeln(` ${c.dim}Skipped. Install manually with:${c.reset}`);
|
|
404
|
+
writeln(` ${c.cyan}npm install swytchcode${c.reset}`);
|
|
405
|
+
writeln(` ${c.dim}For global access:${c.reset} ${c.cyan}npm install -g swytchcode${c.reset}`);
|
|
406
|
+
} else {
|
|
407
|
+
const installSpin = spinner('Running npm install swytchcode ...');
|
|
408
|
+
try {
|
|
409
|
+
execSync('npm install swytchcode', { stdio: 'pipe' });
|
|
410
|
+
installSpin.stop('Installed successfully');
|
|
411
|
+
} catch (e) {
|
|
412
|
+
installSpin.fail('Installation failed.');
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
writeln();
|
|
416
|
+
writeln(` ${c.dim}To make it available globally, run:${c.reset}`);
|
|
417
|
+
writeln(` ${c.bold}${c.green}npm install -g swytchcode${c.reset}`);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// ── Step 3: Account sync ──────────────────────────────────────────────────
|
|
421
|
+
writeln();
|
|
422
|
+
writeln(' ' + '─'.repeat(45));
|
|
423
|
+
writeln();
|
|
424
|
+
writeln(`${c.bold} Step 3 of 4 — Connect your account${c.reset}`);
|
|
425
|
+
writeln(`${c.dim} See your usage analytics, execution history, and saved projects.${c.reset}`);
|
|
426
|
+
writeln();
|
|
427
|
+
|
|
428
|
+
const authChoice = await menuArrow('Do you already have an account?', [
|
|
429
|
+
{ label: 'Login to my account', value: 'login' },
|
|
430
|
+
{ label: 'Create a new account', value: 'register' },
|
|
431
|
+
{ label: 'Continue anonymously', value: 'anon' },
|
|
432
|
+
]);
|
|
433
|
+
|
|
434
|
+
writeln();
|
|
435
|
+
|
|
436
|
+
if (authChoice === 'login') {
|
|
437
|
+
writeln(` ${c.dim}Opening browser for login ...${c.reset}`);
|
|
438
|
+
writeln();
|
|
439
|
+
const result = spawnSync(binPath, ['login'], { stdio: 'inherit' });
|
|
440
|
+
if (result.error || result.status !== 0) {
|
|
441
|
+
writeln(` ${c.yellow}Login window closed or failed.${c.reset}`);
|
|
442
|
+
writeln(` ${c.dim}You can log in later with:${c.reset} ${c.cyan}swytchcode login${c.reset}`);
|
|
443
|
+
} else {
|
|
444
|
+
writeln(` ${c.green}✔ Logged in successfully.${c.reset}`);
|
|
445
|
+
}
|
|
446
|
+
} else if (authChoice === 'register') {
|
|
447
|
+
writeln(` ${c.dim}Opening browser to create your account ...${c.reset}`);
|
|
448
|
+
writeln();
|
|
449
|
+
// login command handles both login and registration via the browser flow
|
|
450
|
+
const result = spawnSync(binPath, ['login'], { stdio: 'inherit' });
|
|
451
|
+
if (result.error || result.status !== 0) {
|
|
452
|
+
writeln(` ${c.yellow}Browser window closed or failed.${c.reset}`);
|
|
453
|
+
writeln(` ${c.dim}Create your account at:${c.reset} ${c.cyan}https://swytchcode.com/signup${c.reset}`);
|
|
454
|
+
} else {
|
|
455
|
+
writeln(` ${c.green}✔ Account connected successfully.${c.reset}`);
|
|
456
|
+
}
|
|
457
|
+
} else {
|
|
458
|
+
writeln(` ${c.dim}No problem — you can connect your account any time with:${c.reset}`);
|
|
459
|
+
writeln(` ${c.cyan}swytchcode login${c.reset}`);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// ── Step 4: Next action ───────────────────────────────────────────────────
|
|
463
|
+
writeln();
|
|
464
|
+
writeln(' ' + '─'.repeat(45));
|
|
465
|
+
writeln();
|
|
466
|
+
writeln(`${c.bold} Step 4 of 4 — What would you like to do next?${c.reset}`);
|
|
467
|
+
writeln();
|
|
468
|
+
|
|
469
|
+
const nextAction = await menuArrow('Choose an action:', [
|
|
470
|
+
{ label: `${c.bold}Build a new integration${c.reset} ${c.dim}— run swytchcode init in your project${c.reset}`, value: 'init' },
|
|
471
|
+
{ label: `${c.bold}Explore examples${c.reset} ${c.dim}— scaffold a working example project${c.reset}`, value: 'examples' },
|
|
472
|
+
{ label: `${c.bold}Exit${c.reset}`, value: 'exit' },
|
|
473
|
+
]);
|
|
474
|
+
|
|
475
|
+
writeln();
|
|
476
|
+
|
|
477
|
+
if (nextAction === 'init') {
|
|
478
|
+
writeln(` ${c.dim}Starting project setup ...${c.reset}`);
|
|
479
|
+
writeln();
|
|
480
|
+
const result = spawnSync(binPath, ['init'], { stdio: 'inherit' });
|
|
481
|
+
if (result.error) {
|
|
482
|
+
writeln(` ${c.red}Could not launch swytchcode init: ${result.error.message}${c.reset}`);
|
|
483
|
+
}
|
|
484
|
+
} else if (nextAction === 'examples') {
|
|
485
|
+
writeln(` ${c.dim}Loading examples ...${c.reset}`);
|
|
486
|
+
writeln();
|
|
487
|
+
const result = spawnSync(binPath, ['examples'], { stdio: 'inherit' });
|
|
488
|
+
if (result.error) {
|
|
489
|
+
writeln(` ${c.red}Could not launch swytchcode examples: ${result.error.message}${c.reset}`);
|
|
490
|
+
}
|
|
491
|
+
} else {
|
|
492
|
+
writeln(` ${c.bold}You're all set.${c.reset}`);
|
|
493
|
+
writeln();
|
|
494
|
+
writeln(` Quick reference:`);
|
|
495
|
+
writeln(` ${c.cyan}swytchcode init${c.reset} ${c.dim}Set up a new project${c.reset}`);
|
|
496
|
+
writeln(` ${c.cyan}swytchcode examples${c.reset} ${c.dim}Browse example integrations${c.reset}`);
|
|
497
|
+
writeln(` ${c.cyan}swytchcode get <api>${c.reset} ${c.dim}Install an API integration${c.reset}`);
|
|
498
|
+
writeln(` ${c.cyan}swytchcode exec <id>${c.reset} ${c.dim}Execute an API method${c.reset}`);
|
|
499
|
+
writeln(` ${c.cyan}swytchcode --help${c.reset} ${c.dim}Full command reference${c.reset}`);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
writeln();
|
|
503
|
+
writeln(` ${c.dim}Docs: https://cli.swytchcode.com${c.reset}`);
|
|
504
|
+
writeln();
|
|
94
505
|
}
|
|
95
506
|
|
|
96
|
-
|
|
507
|
+
runOnboarding().catch(e => {
|
|
508
|
+
// If onboarding crashes for any reason, fall back to normal binary pass-through
|
|
509
|
+
err(`\nswytchcode: onboarding error (${e.message}) — falling back to CLI.\n`);
|
|
510
|
+
const result = spawnBin(binPath, userArgs);
|
|
511
|
+
process.exit(exitCode(result));
|
|
512
|
+
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "swytchcode",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "Execution layer for API integrations",
|
|
3
|
+
"version": "2.5.1",
|
|
4
|
+
"description": "Execution authority layer for API integrations",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"api",
|
|
7
7
|
"integration",
|
|
@@ -23,11 +23,11 @@
|
|
|
23
23
|
"README.md"
|
|
24
24
|
],
|
|
25
25
|
"optionalDependencies": {
|
|
26
|
-
"swytchcode-cli-darwin-arm64": "2.
|
|
27
|
-
"swytchcode-cli-darwin-x64": "2.
|
|
28
|
-
"swytchcode-cli-linux-arm64": "2.
|
|
29
|
-
"swytchcode-cli-linux-x64": "2.
|
|
30
|
-
"swytchcode-cli-win32-arm64": "2.
|
|
31
|
-
"swytchcode-cli-win32-x64": "2.
|
|
26
|
+
"swytchcode-cli-darwin-arm64": "2.5.1",
|
|
27
|
+
"swytchcode-cli-darwin-x64": "2.5.1",
|
|
28
|
+
"swytchcode-cli-linux-arm64": "2.5.1",
|
|
29
|
+
"swytchcode-cli-linux-x64": "2.5.1",
|
|
30
|
+
"swytchcode-cli-win32-arm64": "2.5.1",
|
|
31
|
+
"swytchcode-cli-win32-x64": "2.5.1"
|
|
32
32
|
}
|
|
33
33
|
}
|