airterm 1.1.0 → 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 +78 -11
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -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,6 +284,8 @@ 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";
@@ -312,6 +322,26 @@ function connectSSH(conn, command) {
312
322
 
313
323
  // src/components/Connecting.tsx
314
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
+ }
315
345
  function Connecting({ code, connection, onError }) {
316
346
  const [status, setStatus] = useState4(
317
347
  code ? "Redeeming access code..." : "Connecting..."
@@ -321,6 +351,7 @@ function Connecting({ code, connection, onError }) {
321
351
  let cancelled = false;
322
352
  async function run() {
323
353
  let conn = connection;
354
+ const isNewConnection = !!code;
324
355
  if (code && !conn) {
325
356
  const result = await redeemCode(code);
326
357
  if (cancelled) return;
@@ -358,6 +389,36 @@ function Connecting({ code, connection, onError }) {
358
389
  await new Promise((r) => setTimeout(r, 200));
359
390
  if (cancelled) return;
360
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
+ }
361
422
  const exitCode = connectSSH(conn);
362
423
  process.exit(exitCode);
363
424
  }
@@ -476,6 +537,7 @@ function showHelp(exitCode = 0) {
476
537
 
477
538
  Usage:
478
539
  airterm Connect to your machine
540
+ airterm <code> Redeem an access code and connect
479
541
  airterm <command...> Run a command on your machine
480
542
 
481
543
  Management:
@@ -486,6 +548,7 @@ function showHelp(exitCode = 0) {
486
548
  -v, --version Show version
487
549
 
488
550
  Examples:
551
+ airterm ata_abc123... Redeem an access code
489
552
  airterm Open an interactive SSH session
490
553
  airterm ls -la List files on your machine
491
554
  airterm cat /tmp/log View a remote file
@@ -523,18 +586,22 @@ if (first === "--list" || first === "-l") {
523
586
  render(/* @__PURE__ */ jsx8(App, { initialScreen: "add" }));
524
587
  }
525
588
  } else if (args.length > 0) {
526
- const remoteCmd = first === "--" ? args.slice(1) : args;
527
- if (remoteCmd.length === 0) showHelp(1);
528
- const connections = getConnections();
529
- if (connections.length === 0) {
530
- console.error(
531
- "No saved connections. Run `airterm --add <code>` to set up first."
532
- );
533
- process.exit(1);
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);
534
604
  }
535
- const conn = connections[0];
536
- const exitCode = connectSSH(conn, remoteCmd);
537
- process.exit(exitCode);
538
605
  } else {
539
606
  render(/* @__PURE__ */ jsx8(App, {}));
540
607
  }
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "airterm",
3
- "version": "1.1.0",
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"