ai-remote 0.4.6 → 0.4.7

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.mjs +40 -4
  2. package/package.json +1 -1
package/dist/cli.mjs CHANGED
@@ -1717,8 +1717,16 @@ var Shell = class {
1717
1717
  * rejects that, and the engine notes the shell family and asks to be called
1718
1718
  * again. That is a detail of how the shell was discovered, not something a
1719
1719
  * caller did wrong, so the retry happens here instead of reaching them.
1720
+ *
1721
+ * The wait in front is the same idea one step earlier. "Connected" and "able
1722
+ * to take a command" are not the same moment on Windows: OpenSSH hands out
1723
+ * cmd.exe, the engine swaps it for PowerShell, and readiness is withdrawn in
1724
+ * between. A caller arriving in that window used to be told the shell was
1725
+ * still starting -- true, and useless, because the only thing to do about it
1726
+ * is wait. So it waits.
1720
1727
  */
1721
1728
  async runCommand(command) {
1729
+ await this.#waitForPrompt();
1722
1730
  let result;
1723
1731
  try {
1724
1732
  result = await this.session.runCommand(command);
@@ -1735,14 +1743,19 @@ var Shell = class {
1735
1743
  durationMs: result.durationMs ?? null
1736
1744
  };
1737
1745
  }
1738
- /** Wait until the shell will accept a framed command again. */
1746
+ /**
1747
+ * Wait until the shell will accept a framed command.
1748
+ *
1749
+ * Returns on the first check when the shell is already settled, which is the
1750
+ * usual case, so this costs a waiting caller nothing.
1751
+ */
1739
1752
  async #waitForPrompt(timeoutMs = 15e3) {
1740
1753
  const deadline = Date.now() + timeoutMs;
1741
1754
  while (Date.now() < deadline) {
1742
1755
  if (this.session.shellOpen && this.session.readySignaled !== false) return;
1743
1756
  await new Promise((resolve) => setTimeout(resolve, 100));
1744
1757
  }
1745
- throw new Error("The SSH shell did not come back after its family was discovered.");
1758
+ throw new Error("The SSH shell did not become ready for a command.");
1746
1759
  }
1747
1760
  /** Raw keystrokes, for an interactive terminal. */
1748
1761
  write(text) {
@@ -7753,7 +7766,7 @@ function request(path, op, args = {}, timeoutMs = 12e4) {
7753
7766
  async function runDaemon(options) {
7754
7767
  ensureRunDir();
7755
7768
  const session = new RdpSession(options);
7756
- const size = await session.connect();
7769
+ const desktopStarting = session.connect();
7757
7770
  let shell = null;
7758
7771
  let shellStarting = null;
7759
7772
  function openShell() {
@@ -7775,6 +7788,29 @@ async function runDaemon(options) {
7775
7788
  });
7776
7789
  return shellStarting;
7777
7790
  }
7791
+ function terminalListening(timeoutMs = 2e3) {
7792
+ return new Promise((resolve) => {
7793
+ const socket = net3.connect({ host: options.host, port: options.sshPort });
7794
+ const answer = (listening) => {
7795
+ socket.destroy();
7796
+ resolve(listening);
7797
+ };
7798
+ socket.setTimeout(timeoutMs);
7799
+ socket.on("connect", () => answer(true));
7800
+ socket.on("timeout", () => answer(false));
7801
+ socket.on("error", () => answer(false));
7802
+ });
7803
+ }
7804
+ const warming = (async () => {
7805
+ if (!options.password) return;
7806
+ if (!(options.sshUsername || bareUsername(options.username))) return;
7807
+ if (!await terminalListening()) return;
7808
+ await openShell();
7809
+ })();
7810
+ warming.catch((error) => {
7811
+ console.log(`[ssh] no warm terminal (${error instanceof Error ? error.message : String(error)}); exec will open one when it is needed`);
7812
+ });
7813
+ const size = await desktopStarting;
7778
7814
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
7779
7815
  function writeMeta() {
7780
7816
  writeFileSync2(metaPath(options.name), JSON.stringify({
@@ -8066,7 +8102,7 @@ function freeName(base, taken) {
8066
8102
  // src/cli/cli.ts
8067
8103
  function readLog(name) {
8068
8104
  try {
8069
- const lines = readFileSync2(logPath(name), "utf8").split("\n").filter((line) => /[a-z]/i.test(line) && !/^\s/.test(line) && !line.startsWith("[RDP] ")).map((line) => line.trim());
8105
+ const lines = readFileSync2(logPath(name), "utf8").split("\n").filter((line) => /[a-z]/i.test(line) && !/^\s/.test(line) && !line.startsWith("[RDP] ") && !line.startsWith("[ssh] ")).map((line) => line.trim());
8070
8106
  return [...new Set(lines)].slice(-3).join("\n");
8071
8107
  } catch {
8072
8108
  return "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-remote",
3
- "version": "0.4.6",
3
+ "version": "0.4.7",
4
4
  "description": "RDP, VNC and SSH protocol engines that run in Node, a Worker or a browser -- plus `npx ai-remote`, a CLI that drives a machine and shows you what it is doing",
5
5
  "type": "module",
6
6
  "bin": {