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.
Files changed (2) hide show
  1. package/dist/cli.js +138 -61
  2. 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 meow from "meow";
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 args = [
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
- args.push(
307
+ args2.push(
298
308
  "-o",
299
309
  `ProxyCommand openssl s_client -connect %h:%p -quiet 2>/dev/null`
300
310
  );
301
311
  }
302
- args.push(`${conn.username}@${conn.hostname}`);
303
- const result = spawnSync("ssh", args, { stdio: "inherit" });
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: initialCode2, initialScreen: initialScreen2 }) {
467
+ function App({ initialCode, initialScreen }) {
401
468
  const connections = getConnections();
402
469
  let startScreen;
403
- if (initialCode2) {
404
- startScreen = { type: "connecting", code: initialCode2 };
405
- } else if (initialScreen2 === "add") {
470
+ if (initialCode) {
471
+ startScreen = { type: "connecting", code: initialCode };
472
+ } else if (initialScreen === "add") {
406
473
  startScreen = { type: "add" };
407
- } else if (initialScreen2 === "list") {
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
- if (process.argv.includes("-h")) {
464
- process.argv[process.argv.indexOf("-h")] = "--help";
465
- }
466
- if (process.argv.includes("-v")) {
467
- process.argv[process.argv.indexOf("-v")] = "--version";
468
- }
469
- var cli = meow(
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 Connect to your machine
473
- airterm <code> Redeem an access code and connect
474
- airterm add [code] Add a machine with an access code
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
- Options:
479
- -h, --help Show this help
480
- -v, --version Show version
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
- First time? Run \`airterm\` and paste the access code from your AirClaw agent.
483
- `,
484
- {
485
- importMeta: import.meta,
486
- flags: {}
487
- }
488
- );
489
- var command = cli.input[0];
490
- if (command === "reset") {
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 (command === "help") {
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` to set up.");
577
+ console.log("No saved connections. Run `airterm --add <code>` to set up.");
518
578
  process.exit(0);
519
579
  }
520
- initialScreen = "list";
521
- } else if (command && !["add", "list", "ls", "help", "reset"].includes(command)) {
522
- if (/^[A-Za-z0-9_-]{10,}$/.test(command)) {
523
- initialCode = command;
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
- console.error(`Unknown command: ${command}
526
- `);
527
- cli.showHelp(1);
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",
3
+ "version": "1.2.0",
4
4
  "description": "SSH into your AirClaw machine",
5
5
  "type": "module",
6
6
  "bin": {
7
- "airterm": "./dist/cli.js"
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": {