airterm 1.0.3 → 1.2.0
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/dist/cli.js +138 -61
- package/package.json +2 -3
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli.tsx
|
|
4
4
|
import { render } from "ink";
|
|
5
|
-
import
|
|
5
|
+
import { createRequire } from "module";
|
|
6
6
|
|
|
7
7
|
// src/components/App.tsx
|
|
8
8
|
import { useState as useState5 } from "react";
|
|
@@ -81,6 +81,14 @@ function resetAll() {
|
|
|
81
81
|
}
|
|
82
82
|
return count;
|
|
83
83
|
}
|
|
84
|
+
function wasGlobalInstallDeclined() {
|
|
85
|
+
return loadConfig().globalInstallDeclined === true;
|
|
86
|
+
}
|
|
87
|
+
function setGlobalInstallDeclined() {
|
|
88
|
+
const config = loadConfig();
|
|
89
|
+
config.globalInstallDeclined = true;
|
|
90
|
+
saveConfig(config);
|
|
91
|
+
}
|
|
84
92
|
|
|
85
93
|
// src/components/Welcome.tsx
|
|
86
94
|
import { useState, useEffect } from "react";
|
|
@@ -276,12 +284,14 @@ function SelectMachine({
|
|
|
276
284
|
import { useEffect as useEffect2, useState as useState4 } from "react";
|
|
277
285
|
import { Box as Box5, Text as Text5, useApp as useApp2 } from "ink";
|
|
278
286
|
import Spinner from "ink-spinner";
|
|
287
|
+
import { execFileSync } from "child_process";
|
|
288
|
+
import { createInterface } from "readline";
|
|
279
289
|
|
|
280
290
|
// src/lib/ssh.ts
|
|
281
291
|
import { spawnSync } from "child_process";
|
|
282
|
-
function connectSSH(conn) {
|
|
292
|
+
function connectSSH(conn, command) {
|
|
283
293
|
const needsTls = conn.hostname.endsWith(".fly.dev");
|
|
284
|
-
const
|
|
294
|
+
const args2 = [
|
|
285
295
|
"-i",
|
|
286
296
|
conn.keyPath,
|
|
287
297
|
"-p",
|
|
@@ -294,18 +304,44 @@ function connectSSH(conn) {
|
|
|
294
304
|
"LogLevel=ERROR"
|
|
295
305
|
];
|
|
296
306
|
if (needsTls) {
|
|
297
|
-
|
|
307
|
+
args2.push(
|
|
298
308
|
"-o",
|
|
299
309
|
`ProxyCommand openssl s_client -connect %h:%p -quiet 2>/dev/null`
|
|
300
310
|
);
|
|
301
311
|
}
|
|
302
|
-
|
|
303
|
-
|
|
312
|
+
if (command && command.length > 0 && process.stdin.isTTY) {
|
|
313
|
+
args2.push("-t");
|
|
314
|
+
}
|
|
315
|
+
args2.push(`${conn.username}@${conn.hostname}`);
|
|
316
|
+
if (command && command.length > 0) {
|
|
317
|
+
args2.push("--", ...command);
|
|
318
|
+
}
|
|
319
|
+
const result = spawnSync("ssh", args2, { stdio: "inherit" });
|
|
304
320
|
return result.status ?? 1;
|
|
305
321
|
}
|
|
306
322
|
|
|
307
323
|
// src/components/Connecting.tsx
|
|
308
324
|
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
325
|
+
function isGloballyInstalled() {
|
|
326
|
+
try {
|
|
327
|
+
const p = execFileSync("which", ["airterm"], {
|
|
328
|
+
encoding: "utf-8",
|
|
329
|
+
stdio: ["pipe", "pipe", "ignore"]
|
|
330
|
+
}).trim();
|
|
331
|
+
return !!p && !p.includes("_npx") && !p.includes(".dlx");
|
|
332
|
+
} catch {
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
function ask(question) {
|
|
337
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
338
|
+
return new Promise((resolve) => {
|
|
339
|
+
rl.question(question, (answer) => {
|
|
340
|
+
rl.close();
|
|
341
|
+
resolve(answer.trim().toLowerCase());
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
}
|
|
309
345
|
function Connecting({ code, connection, onError }) {
|
|
310
346
|
const [status, setStatus] = useState4(
|
|
311
347
|
code ? "Redeeming access code..." : "Connecting..."
|
|
@@ -315,6 +351,7 @@ function Connecting({ code, connection, onError }) {
|
|
|
315
351
|
let cancelled = false;
|
|
316
352
|
async function run() {
|
|
317
353
|
let conn = connection;
|
|
354
|
+
const isNewConnection = !!code;
|
|
318
355
|
if (code && !conn) {
|
|
319
356
|
const result = await redeemCode(code);
|
|
320
357
|
if (cancelled) return;
|
|
@@ -352,6 +389,36 @@ function Connecting({ code, connection, onError }) {
|
|
|
352
389
|
await new Promise((r) => setTimeout(r, 200));
|
|
353
390
|
if (cancelled) return;
|
|
354
391
|
exit();
|
|
392
|
+
if (isNewConnection) {
|
|
393
|
+
console.log("");
|
|
394
|
+
console.log(
|
|
395
|
+
"Connected to your AirClaw. From now on:"
|
|
396
|
+
);
|
|
397
|
+
console.log(" airterm sign in to your machine");
|
|
398
|
+
console.log(" airterm ls -la run commands directly");
|
|
399
|
+
console.log("");
|
|
400
|
+
if (!isGloballyInstalled() && !wasGlobalInstallDeclined()) {
|
|
401
|
+
const answer = await ask(
|
|
402
|
+
"Install airterm globally so you can just type `airterm`? (y/n) "
|
|
403
|
+
);
|
|
404
|
+
if (answer === "y" || answer === "yes") {
|
|
405
|
+
console.log("");
|
|
406
|
+
try {
|
|
407
|
+
execFileSync("npm", ["install", "-g", "airterm"], {
|
|
408
|
+
stdio: "inherit"
|
|
409
|
+
});
|
|
410
|
+
console.log("\nNext time, just type `airterm`.\n");
|
|
411
|
+
} catch {
|
|
412
|
+
console.log(
|
|
413
|
+
"\nInstall failed. You can try manually: npm install -g airterm\n"
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
} else {
|
|
417
|
+
setGlobalInstallDeclined();
|
|
418
|
+
console.log("No problem. Run `npx airterm` anytime.\n");
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
355
422
|
const exitCode = connectSSH(conn);
|
|
356
423
|
process.exit(exitCode);
|
|
357
424
|
}
|
|
@@ -397,14 +464,14 @@ function ErrorView({ message }) {
|
|
|
397
464
|
|
|
398
465
|
// src/components/App.tsx
|
|
399
466
|
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
400
|
-
function App({ initialCode
|
|
467
|
+
function App({ initialCode, initialScreen }) {
|
|
401
468
|
const connections = getConnections();
|
|
402
469
|
let startScreen;
|
|
403
|
-
if (
|
|
404
|
-
startScreen = { type: "connecting", code:
|
|
405
|
-
} else if (
|
|
470
|
+
if (initialCode) {
|
|
471
|
+
startScreen = { type: "connecting", code: initialCode };
|
|
472
|
+
} else if (initialScreen === "add") {
|
|
406
473
|
startScreen = { type: "add" };
|
|
407
|
-
} else if (
|
|
474
|
+
} else if (initialScreen === "list") {
|
|
408
475
|
startScreen = { type: "select" };
|
|
409
476
|
} else if (connections.length === 0) {
|
|
410
477
|
startScreen = { type: "welcome" };
|
|
@@ -460,71 +527,81 @@ function App({ initialCode: initialCode2, initialScreen: initialScreen2 }) {
|
|
|
460
527
|
|
|
461
528
|
// src/cli.tsx
|
|
462
529
|
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
530
|
+
var require2 = createRequire(import.meta.url);
|
|
531
|
+
var pkg = require2("../package.json");
|
|
532
|
+
var args = process.argv.slice(2);
|
|
533
|
+
var first = args[0];
|
|
534
|
+
function showHelp(exitCode = 0) {
|
|
535
|
+
console.log(`
|
|
536
|
+
airterm v${pkg.version} \u2014 ${pkg.description}
|
|
537
|
+
|
|
471
538
|
Usage:
|
|
472
|
-
airterm
|
|
473
|
-
airterm <code>
|
|
474
|
-
airterm
|
|
475
|
-
airterm list Manage saved connections
|
|
476
|
-
airterm reset Remove all saved connections and keys
|
|
539
|
+
airterm Connect to your machine
|
|
540
|
+
airterm <code> Redeem an access code and connect
|
|
541
|
+
airterm <command...> Run a command on your machine
|
|
477
542
|
|
|
478
|
-
|
|
479
|
-
-
|
|
480
|
-
-
|
|
543
|
+
Management:
|
|
544
|
+
-a, --add <code> Add a machine with an access code
|
|
545
|
+
-l, --list Manage saved connections
|
|
546
|
+
-r, --reset Remove all saved connections
|
|
547
|
+
-h, --help Show this help
|
|
548
|
+
-v, --version Show version
|
|
481
549
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
);
|
|
489
|
-
|
|
490
|
-
|
|
550
|
+
Examples:
|
|
551
|
+
airterm ata_abc123... Redeem an access code
|
|
552
|
+
airterm Open an interactive SSH session
|
|
553
|
+
airterm ls -la List files on your machine
|
|
554
|
+
airterm cat /tmp/log View a remote file
|
|
555
|
+
airterm openclaw tui Launch OpenClaw TUI
|
|
556
|
+
`);
|
|
557
|
+
process.exit(exitCode);
|
|
558
|
+
}
|
|
559
|
+
if (first === "--help" || first === "-h") showHelp();
|
|
560
|
+
if (first === "--version" || first === "-v") {
|
|
561
|
+
console.log(pkg.version);
|
|
562
|
+
process.exit(0);
|
|
563
|
+
}
|
|
564
|
+
if (first === "--reset" || first === "-r") {
|
|
491
565
|
const count = resetAll();
|
|
492
566
|
if (count > 0) {
|
|
493
567
|
console.log(
|
|
494
568
|
`Removed ${count} saved connection${count !== 1 ? "s" : ""} and keys.`
|
|
495
569
|
);
|
|
496
570
|
}
|
|
497
|
-
console.log(
|
|
498
|
-
"AirTerm data wiped. Run `airterm add` to set up again."
|
|
499
|
-
);
|
|
571
|
+
console.log("AirTerm data wiped. Run `airterm --add <code>` to set up again.");
|
|
500
572
|
process.exit(0);
|
|
501
573
|
}
|
|
502
|
-
if (
|
|
503
|
-
cli.showHelp(0);
|
|
504
|
-
}
|
|
505
|
-
var initialCode;
|
|
506
|
-
var initialScreen;
|
|
507
|
-
if (command === "add") {
|
|
508
|
-
const code = cli.input[1];
|
|
509
|
-
if (code) {
|
|
510
|
-
initialCode = code;
|
|
511
|
-
} else {
|
|
512
|
-
initialScreen = "add";
|
|
513
|
-
}
|
|
514
|
-
} else if (command === "list" || command === "ls") {
|
|
574
|
+
if (first === "--list" || first === "-l") {
|
|
515
575
|
const connections = getConnections();
|
|
516
576
|
if (connections.length === 0) {
|
|
517
|
-
console.log("No saved connections. Run `airterm add
|
|
577
|
+
console.log("No saved connections. Run `airterm --add <code>` to set up.");
|
|
518
578
|
process.exit(0);
|
|
519
579
|
}
|
|
520
|
-
initialScreen
|
|
521
|
-
} else if (
|
|
522
|
-
|
|
523
|
-
|
|
580
|
+
render(/* @__PURE__ */ jsx8(App, { initialScreen: "list" }));
|
|
581
|
+
} else if (first === "--add" || first === "-a") {
|
|
582
|
+
const code = args[1];
|
|
583
|
+
if (code) {
|
|
584
|
+
render(/* @__PURE__ */ jsx8(App, { initialCode: code }));
|
|
524
585
|
} else {
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
586
|
+
render(/* @__PURE__ */ jsx8(App, { initialScreen: "add" }));
|
|
587
|
+
}
|
|
588
|
+
} else if (args.length > 0) {
|
|
589
|
+
if (first.startsWith("ata_")) {
|
|
590
|
+
render(/* @__PURE__ */ jsx8(App, { initialCode: first }));
|
|
591
|
+
} else {
|
|
592
|
+
const remoteCmd = first === "--" ? args.slice(1) : args;
|
|
593
|
+
if (remoteCmd.length === 0) showHelp(1);
|
|
594
|
+
const connections = getConnections();
|
|
595
|
+
if (connections.length === 0) {
|
|
596
|
+
console.error(
|
|
597
|
+
"No saved connections. Run `airterm --add <code>` to set up first."
|
|
598
|
+
);
|
|
599
|
+
process.exit(1);
|
|
600
|
+
}
|
|
601
|
+
const conn = connections[0];
|
|
602
|
+
const exitCode = connectSSH(conn, remoteCmd);
|
|
603
|
+
process.exit(exitCode);
|
|
528
604
|
}
|
|
605
|
+
} else {
|
|
606
|
+
render(/* @__PURE__ */ jsx8(App, {}));
|
|
529
607
|
}
|
|
530
|
-
render(/* @__PURE__ */ jsx8(App, { initialCode, initialScreen }));
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "airterm",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "SSH into your AirClaw machine",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"airterm": "
|
|
7
|
+
"airterm": "dist/cli.js"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"dist"
|
|
@@ -19,7 +19,6 @@
|
|
|
19
19
|
"ink-select-input": "^6.0.0",
|
|
20
20
|
"ink-spinner": "^5.0.0",
|
|
21
21
|
"ink-text-input": "^6.0.0",
|
|
22
|
-
"meow": "^13.0.0",
|
|
23
22
|
"react": "^18.3.1"
|
|
24
23
|
},
|
|
25
24
|
"devDependencies": {
|