airterm 1.0.0 → 1.0.2

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 -52
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -83,7 +83,7 @@ function resetAll() {
83
83
  }
84
84
 
85
85
  // src/components/Welcome.tsx
86
- import { useState } from "react";
86
+ import { useState, useEffect } from "react";
87
87
  import { Box as Box2, Text as Text2 } from "ink";
88
88
  import TextInput from "ink-text-input";
89
89
 
@@ -94,10 +94,50 @@ function Header() {
94
94
  return /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ jsx(Box, { borderStyle: "round", paddingX: 2, children: /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "AirTerm" }) }) });
95
95
  }
96
96
 
97
+ // src/lib/api.ts
98
+ var API_BASE = process.env.AIRCLAW_API_URL || "https://app.airclaw.com";
99
+ async function redeemCode(code) {
100
+ const res = await fetch(`${API_BASE}/api/airterm/redeem`, {
101
+ method: "POST",
102
+ headers: { "Content-Type": "application/json" },
103
+ body: JSON.stringify({ code })
104
+ });
105
+ const data = await res.json();
106
+ if (!res.ok) {
107
+ return { error: data.error || `HTTP ${res.status}` };
108
+ }
109
+ return data;
110
+ }
111
+ function isRedeemError(result) {
112
+ return "error" in result;
113
+ }
114
+ async function downloadKey(url) {
115
+ const res = await fetch(url);
116
+ if (!res.ok) {
117
+ throw new Error(`Failed to download key: HTTP ${res.status}`);
118
+ }
119
+ return await res.text();
120
+ }
121
+ async function fetchInfo() {
122
+ try {
123
+ const res = await fetch(`${API_BASE}/api/airterm/info`);
124
+ if (!res.ok) return null;
125
+ return await res.json();
126
+ } catch {
127
+ return null;
128
+ }
129
+ }
130
+
97
131
  // src/components/Welcome.tsx
98
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
132
+ import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
99
133
  function Welcome({ onSubmitCode }) {
100
134
  const [code, setCode] = useState("");
135
+ const [phone, setPhone] = useState(null);
136
+ useEffect(() => {
137
+ fetchInfo().then((info) => {
138
+ if (info?.phone) setPhone(info.phone);
139
+ });
140
+ }, []);
101
141
  return /* @__PURE__ */ jsxs(Box2, { flexDirection: "column", children: [
102
142
  /* @__PURE__ */ jsx2(Header, {}),
103
143
  /* @__PURE__ */ jsx2(Box2, { marginBottom: 1, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "No saved connections." }) }),
@@ -117,11 +157,14 @@ function Welcome({ onSubmitCode }) {
117
157
  }
118
158
  )
119
159
  ] }),
120
- /* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsxs(Text2, { dimColor: true, children: [
121
- "Don't have one? Text your AirClaw:",
122
- "\n",
123
- "\u2192 sms:+14156058331&body=Give%20me%20terminal%20access"
124
- ] }) })
160
+ /* @__PURE__ */ jsx2(Box2, { marginTop: 1, flexDirection: "column", children: phone ? /* @__PURE__ */ jsxs(Fragment, { children: [
161
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Don't have one? Text your AirClaw:" }),
162
+ /* @__PURE__ */ jsxs(Text2, { color: "cyan", children: [
163
+ "\u2192 sms:",
164
+ phone,
165
+ "&body=Give%20me%20terminal%20access"
166
+ ] })
167
+ ] }) : /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Don't have one? Text your AirClaw and ask for terminal access." }) })
125
168
  ] });
126
169
  }
127
170
 
@@ -232,53 +275,32 @@ function SelectMachine({
232
275
  }
233
276
 
234
277
  // src/components/Connecting.tsx
235
- import { useEffect, useState as useState4 } from "react";
278
+ import { useEffect as useEffect2, useState as useState4 } from "react";
236
279
  import { Box as Box5, Text as Text5, useApp as useApp2 } from "ink";
237
280
  import Spinner from "ink-spinner";
238
281
 
239
- // src/lib/api.ts
240
- var API_BASE = process.env.AIRCLAW_API_URL || "https://app.airclaw.com";
241
- async function redeemCode(code) {
242
- const res = await fetch(`${API_BASE}/api/airterm/redeem`, {
243
- method: "POST",
244
- headers: { "Content-Type": "application/json" },
245
- body: JSON.stringify({ code })
246
- });
247
- const data = await res.json();
248
- if (!res.ok) {
249
- return { error: data.error || `HTTP ${res.status}` };
250
- }
251
- return data;
252
- }
253
- function isRedeemError(result) {
254
- return "error" in result;
255
- }
256
- async function downloadKey(url) {
257
- const res = await fetch(url);
258
- if (!res.ok) {
259
- throw new Error(`Failed to download key: HTTP ${res.status}`);
260
- }
261
- return await res.text();
262
- }
263
-
264
282
  // src/lib/ssh.ts
265
283
  import { spawnSync } from "child_process";
266
284
  function connectSSH(conn) {
267
- const result = spawnSync(
268
- "ssh",
269
- [
270
- "-i",
271
- conn.keyPath,
272
- "-p",
273
- String(conn.port),
285
+ const needsTls = conn.hostname.endsWith(".fly.dev");
286
+ const args = [
287
+ "-i",
288
+ conn.keyPath,
289
+ "-p",
290
+ String(conn.port),
291
+ "-o",
292
+ "StrictHostKeyChecking=accept-new",
293
+ "-o",
294
+ "UserKnownHostsFile=~/.airterm/known_hosts"
295
+ ];
296
+ if (needsTls) {
297
+ args.push(
274
298
  "-o",
275
- "StrictHostKeyChecking=accept-new",
276
- "-o",
277
- "UserKnownHostsFile=~/.airterm/known_hosts",
278
- `${conn.username}@${conn.hostname}`
279
- ],
280
- { stdio: "inherit" }
281
- );
299
+ `ProxyCommand openssl s_client -connect %h:%p -quiet 2>/dev/null`
300
+ );
301
+ }
302
+ args.push(`${conn.username}@${conn.hostname}`);
303
+ const result = spawnSync("ssh", args, { stdio: "inherit" });
282
304
  return result.status ?? 1;
283
305
  }
284
306
 
@@ -289,7 +311,7 @@ function Connecting({ code, connection, onError }) {
289
311
  code ? "Redeeming access code..." : "Connecting..."
290
312
  );
291
313
  const { exit } = useApp2();
292
- useEffect(() => {
314
+ useEffect2(() => {
293
315
  let cancelled = false;
294
316
  async function run() {
295
317
  let conn = connection;
@@ -438,10 +460,14 @@ function App({ initialCode: initialCode2, initialScreen: initialScreen2 }) {
438
460
 
439
461
  // src/cli.tsx
440
462
  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
+ }
441
469
  var cli = meow(
442
470
  `
443
- AirTerm \u2014 SSH into your AirClaw machine
444
-
445
471
  Usage:
446
472
  airterm Connect to your machine
447
473
  airterm <code> Redeem an access code and connect
@@ -485,14 +511,14 @@ if (command === "add") {
485
511
  } else {
486
512
  initialScreen = "add";
487
513
  }
488
- } else if (command === "list") {
514
+ } else if (command === "list" || command === "ls") {
489
515
  const connections = getConnections();
490
516
  if (connections.length === 0) {
491
517
  console.log("No saved connections. Run `airterm add` to set up.");
492
518
  process.exit(0);
493
519
  }
494
520
  initialScreen = "list";
495
- } else if (command && !["add", "list", "help", "reset"].includes(command)) {
521
+ } else if (command && !["add", "list", "ls", "help", "reset"].includes(command)) {
496
522
  if (/^[A-Za-z0-9_-]{10,}$/.test(command)) {
497
523
  initialCode = command;
498
524
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "airterm",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "SSH into your AirClaw machine",
5
5
  "type": "module",
6
6
  "bin": {