ai-remote 0.5.0 → 0.5.1

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.
@@ -53,7 +53,7 @@ async function parsePrivateKey(text, comment = "") {
53
53
  async function parseOpenSshKey(body, comment) {
54
54
  const magic = new TextDecoder().decode(body.subarray(0, OPENSSH_MAGIC.length));
55
55
  if (magic !== OPENSSH_MAGIC) throw new Error("this is not an OpenSSH private key");
56
- const reader = new SshReader(body, OPENSSH_MAGIC.length);
56
+ const reader = new SshReader(body.slice(), OPENSSH_MAGIC.length);
57
57
  const cipher = reader.string();
58
58
  reader.string();
59
59
  reader.stringBytes();
@@ -171,7 +171,7 @@ async function ecdsaIdentity(keyBlob, curve, point, d, comment) {
171
171
  const x = point.subarray(1, 1 + spec.size);
172
172
  const y = point.subarray(1 + spec.size, 1 + spec.size * 2);
173
173
  const key = await importJwk(
174
- { kty: "EC", crv: spec.jwk, x: b64u(x), y: b64u(y), d: b64u(padStart(d, spec.size)) },
174
+ { kty: "EC", crv: spec.jwk, x: b64u(x), y: b64u(y), d: b64u(padStart(d.slice(), spec.size)) },
175
175
  { name: "ECDSA", namedCurve: spec.jwk }
176
176
  );
177
177
  const algorithm = `ecdsa-sha2-${curve}`;
@@ -231,8 +231,8 @@ async function fromPkcs8(der, comment) {
231
231
  const [name, spec] = curve;
232
232
  const point = concatBytes(
233
233
  new Uint8Array([4]),
234
- padStart(from(jwk.x), spec.size),
235
- padStart(from(jwk.y), spec.size)
234
+ padStart(from(jwk.x).slice(), spec.size),
235
+ padStart(from(jwk.y).slice(), spec.size)
236
236
  );
237
237
  return ecdsaIdentity(new Uint8Array(0), name, point, from(jwk.d), comment);
238
238
  }
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  Shell,
3
3
  bareUsername
4
- } from "./cli-chunk-Q33YGIDO.mjs";
5
- import "./cli-chunk-S2TWL5LF.mjs";
4
+ } from "./cli-chunk-M5GGPPAC.mjs";
5
+ import "./cli-chunk-X2JMVOWL.mjs";
6
6
  import "./cli-chunk-XT2FISR5.mjs";
7
7
  export {
8
8
  Shell,
@@ -4,8 +4,8 @@ import {
4
4
  openWindow,
5
5
  readCurrentDisplaySize,
6
6
  runWindow
7
- } from "./cli-chunk-F2TSY3YW.mjs";
8
- import "./cli-chunk-OZG2CODR.mjs";
7
+ } from "./cli-chunk-DGYNADUR.mjs";
8
+ import "./cli-chunk-WBOYG4KJ.mjs";
9
9
  export {
10
10
  detectCurrentDisplaySize,
11
11
  nativeWindowAvailable,
package/dist/cli.mjs CHANGED
@@ -13,12 +13,21 @@ import {
13
13
  metaPath,
14
14
  sessionName,
15
15
  socketPath
16
- } from "./cli-chunk-OZG2CODR.mjs";
16
+ } from "./cli-chunk-WBOYG4KJ.mjs";
17
17
 
18
18
  // src/cli/cli.ts
19
19
  import { resolve as resolvePath } from "node:path";
20
20
  import { readFileSync as readFileSync2, openSync } from "node:fs";
21
21
 
22
+ // src/cli/target.ts
23
+ function splitTarget(target, fallbackPort) {
24
+ const stripped = target.replace(/^\w+:\/\//, "");
25
+ const at = stripped.lastIndexOf("@");
26
+ const account = at === -1 ? "" : stripped.slice(0, at);
27
+ const [host, port] = (at === -1 ? stripped : stripped.slice(at + 1)).split(":");
28
+ return { account, host, port: port ? Number(port) : fallbackPort };
29
+ }
30
+
22
31
  // src/cli/prompt.ts
23
32
  function canPrompt() {
24
33
  return Boolean(process.stdin.isTTY) && !process.env.AI_REMOTE_NO_PROMPT;
@@ -312,11 +321,6 @@ var flag = (args, ...names) => {
312
321
  return void 0;
313
322
  };
314
323
  var has = (args, ...names) => names.some((name) => args.flags[name] === true);
315
- function splitTarget(target, fallbackPort) {
316
- const stripped = target.replace(/^\w+:\/\//, "");
317
- const [host, port] = stripped.split(":");
318
- return { host, port: port ? Number(port) : fallbackPort };
319
- }
320
324
  var PORTS = { rdp: 3389, vnc: 5900, ssh: 22 };
321
325
  function modeFor(args, target) {
322
326
  if (has(args, "ssh", "ssh-only", "terminal")) return "ssh";
@@ -361,7 +365,7 @@ async function promptFor(host, port, username, note = "") {
361
365
  }
362
366
  function identityLoader(paths) {
363
367
  return async () => {
364
- const { loadIdentities } = await import("./cli-identities-ZILT4XCR.mjs");
368
+ const { loadIdentities } = await import("./cli-identities-GBQWYF2O.mjs");
365
369
  const { identities, skipped } = await loadIdentities(paths);
366
370
  for (const note of skipped) console.log(`[ssh] skipped ${note}`);
367
371
  console.log(identities.length ? `[ssh] keys: ${identities.map((identity) => identity.comment).join(", ")}` : "[ssh] no keys to offer");
@@ -430,13 +434,13 @@ async function tell(session, op, args = {}, timeoutMs) {
430
434
  }
431
435
  return result;
432
436
  }
433
- async function startSession(args, name, host, port, secret) {
437
+ async function startSession(args, name, host, port, secret, account = "") {
434
438
  const mode = modeFor(args, `${host}:${port}`);
435
439
  const explicitWidth = flag(args, "W", "width");
436
440
  const explicitHeight = flag(args, "H", "height");
437
441
  let fullscreenSize = null;
438
442
  if (mode === "rdp" && has(args, "fullscreen") && (!explicitWidth || !explicitHeight)) {
439
- const { detectCurrentDisplaySize } = await import("./cli-window-ZKAXEHQV.mjs");
443
+ const { detectCurrentDisplaySize } = await import("./cli-window-WLRFUKSW.mjs");
440
444
  const display = await detectCurrentDisplaySize();
441
445
  if (display) fullscreenSize = fullscreenRdpSize(display, has(args, "side-panel"));
442
446
  }
@@ -453,7 +457,7 @@ async function startSession(args, name, host, port, secret) {
453
457
  "--name",
454
458
  name,
455
459
  "-u",
456
- flag(args, "u", "user") ?? "",
460
+ flag(args, "u", "user") ?? account,
457
461
  "-d",
458
462
  flag(args, "d", "domain") ?? "",
459
463
  "-s",
@@ -463,7 +467,7 @@ async function startSession(args, name, host, port, secret) {
463
467
  "-H",
464
468
  height,
465
469
  "--ssh-user",
466
- flag(args, "ssh-user") ?? flag(args, "u", "user") ?? "",
470
+ flag(args, "ssh-user") ?? flag(args, "u", "user") ?? account,
467
471
  // In a terminal session the target port is the SSH port; there is no other.
468
472
  "--ssh-port",
469
473
  flag(args, "ssh-port") ?? (mode === "ssh" ? String(port) : "22"),
@@ -559,7 +563,7 @@ async function main() {
559
563
  }
560
564
  const json = has(args, "json");
561
565
  if (args.command === "__display-size") {
562
- const { readCurrentDisplaySize } = await import("./cli-window-ZKAXEHQV.mjs");
566
+ const { readCurrentDisplaySize } = await import("./cli-window-WLRFUKSW.mjs");
563
567
  const size = await readCurrentDisplaySize();
564
568
  if (size) process.stdout.write(`${JSON.stringify(size)}
565
569
  `);
@@ -568,7 +572,7 @@ async function main() {
568
572
  if (args.command === "__session") {
569
573
  const mode = modeFor(args, args.rest[0]);
570
574
  const { host, port } = splitTarget(args.rest[0] ?? "", defaultPort(mode));
571
- const { runDaemon } = await import("./cli-daemon-3BBFQCML.mjs");
575
+ const { runDaemon } = await import("./cli-daemon-M3KM2YHW.mjs");
572
576
  await runDaemon({
573
577
  name: sessionName(host, port, flag(args, "name", "session")),
574
578
  host,
@@ -601,7 +605,7 @@ async function main() {
601
605
  return;
602
606
  }
603
607
  if (args.command === "__window") {
604
- const { runWindow } = await import("./cli-window-ZKAXEHQV.mjs");
608
+ const { runWindow } = await import("./cli-window-WLRFUKSW.mjs");
605
609
  await runWindow(process.argv.slice(3));
606
610
  return;
607
611
  }
@@ -653,11 +657,11 @@ Saved, nothing open (\`${NAME} open <host>\` needs no password):
653
657
  throw new CliError(`--keys takes "adapt" or "literal", not "${keys}".`, 12);
654
658
  }
655
659
  const mode = modeFor(args, target);
656
- const { host, port } = splitTarget(target, defaultPort(mode));
660
+ const { account: targetAccount, host, port } = splitTarget(target, defaultPort(mode));
657
661
  const asked = flag(args, "session", "name");
658
662
  const base = sessionName(host, port, asked);
659
663
  const name2 = has(args, "no-reuse") && !asked ? freeName(base, liveSessions().map((meta) => meta.name)) : base;
660
- const account = flag(args, "u", "user") ?? "";
664
+ const account = flag(args, "u", "user") ?? targetAccount;
661
665
  const prompts = prompting(args);
662
666
  let secret = await passwordFor(host, port, account);
663
667
  let prompted = false;
@@ -669,7 +673,7 @@ Saved, nothing open (\`${NAME} open <host>\` needs no password):
669
673
  const reused = Boolean(status);
670
674
  if (!status) {
671
675
  try {
672
- status = await startSession(args, name2, host, port, secret);
676
+ status = await startSession(args, name2, host, port, secret, account);
673
677
  } catch (error) {
674
678
  const refused = error instanceof CliError && error.code === 11;
675
679
  if (!refused || prompted || !prompts) throw error;
@@ -683,7 +687,7 @@ Saved, nothing open (\`${NAME} open <host>\` needs no password):
683
687
  cleanup(name2);
684
688
  secret = typed;
685
689
  prompted = true;
686
- status = await startSession(args, name2, host, port, secret);
690
+ status = await startSession(args, name2, host, port, secret, account);
687
691
  }
688
692
  }
689
693
  const saved = has(args, "save") && secret ? await keepSecret(host, port, account, secret) : null;
@@ -983,7 +987,7 @@ function report(json, command, summary) {
983
987
  `);
984
988
  }
985
989
  async function sshLogin(args, session, what) {
986
- const { bareUsername } = await import("./cli-shell-45XRTE7O.mjs");
990
+ const { bareUsername } = await import("./cli-shell-22PZYNSJ.mjs");
987
991
  const username = bareUsername(flag(args, "ssh-user") ?? session.sshUser ?? "");
988
992
  if (!username) {
989
993
  throw new CliError(
@@ -991,7 +995,7 @@ async function sshLogin(args, session, what) {
991
995
  12
992
996
  );
993
997
  }
994
- const { loadIdentities } = await import("./cli-identities-ZILT4XCR.mjs");
998
+ const { loadIdentities } = await import("./cli-identities-GBQWYF2O.mjs");
995
999
  const { identities, skipped } = await loadIdentities(args.identities);
996
1000
  const port = Number(flag(args, "ssh-port") ?? session.sshPort ?? 22);
997
1001
  let password2 = await passwordFor(session.host, port, username);
@@ -1065,7 +1069,7 @@ async function copyFiles(args, json) {
1065
1069
  upload,
1066
1070
  walkLocal,
1067
1071
  walkRemote
1068
- } = await import("./cli-copy-BOF44DWQ.mjs");
1072
+ } = await import("./cli-copy-ZBTICP7N.mjs");
1069
1073
  const from = resolveEndpoint(args, parseEndpoint(args.rest[0]));
1070
1074
  const to = resolveEndpoint(args, parseEndpoint(args.rest[1]));
1071
1075
  if (!from.endpoint.remote && !to.endpoint.remote) {
@@ -1143,7 +1147,7 @@ async function copyFiles(args, json) {
1143
1147
  }
1144
1148
  }
1145
1149
  async function interactiveShell(args, session) {
1146
- const { Shell } = await import("./cli-shell-45XRTE7O.mjs");
1150
+ const { Shell } = await import("./cli-shell-22PZYNSJ.mjs");
1147
1151
  const login = await sshLogin(args, session, "a terminal");
1148
1152
  const { username, port, identities } = login;
1149
1153
  const prompts = prompting(args);
@@ -139,6 +139,14 @@ interface ScrollDelta {
139
139
  x: number;
140
140
  y: number;
141
141
  }
142
+ /**
143
+ * What the metrics rail instruments. A real WebSocket in the browser; in the
144
+ * CLI, a shim with the same surface over a TCP socket straight at the host.
145
+ */
146
+ interface InstrumentableSocket {
147
+ readonly readyState: number;
148
+ addEventListener(type: string, listener: (event: any) => void): void;
149
+ }
142
150
  /**
143
151
  * A live session. Both drivers expose this, so the AI pane's screen tool and
144
152
  * the toolbar never have to ask which protocol they are driving.
@@ -157,7 +165,7 @@ export interface RemoteSession extends EventTarget {
157
165
  /** RFB compression level. RDP ignores it. */
158
166
  compressionLevel?: number;
159
167
  /** The transport the metrics rail instruments. Can appear a tick after construction. */
160
- readonly socket?: WebSocket | null;
168
+ readonly socket?: InstrumentableSocket | null;
161
169
  readonly screenGeometry: ScreenGeometry;
162
170
  movePointer(x: number, y: number): boolean;
163
171
  clickPointer(x: number, y: number, button?: PointerButton): boolean;
@@ -245,6 +253,45 @@ export interface ProtocolDriver {
245
253
  /** Protocol-specific wording for a close, or null to fall back to the shared text. */
246
254
  describeDisconnect(context: DisconnectContext): string | null;
247
255
  }
256
+ /**
257
+ * Signing in with a key instead of a password.
258
+ *
259
+ * An identity is a public key blob, the algorithm name that goes with it, and
260
+ * something that can sign. What does the signing is deliberately behind a
261
+ * function: a key read off disk signs with Web Crypto, and a key held by
262
+ * ssh-agent signs by asking the agent -- the session cannot tell the two apart
263
+ * and does not need to.
264
+ *
265
+ * The parser here reads the format `ssh-keygen` writes by default, which no
266
+ * runtime can parse on its own: OpenSSH's own container rather than PKCS#8.
267
+ * Only unencrypted keys are read. A key with a passphrase would need bcrypt
268
+ * and a prompt, and the answer to a passphrase is ssh-agent, which is already
269
+ * holding the decrypted key.
270
+ */
271
+ /** One key that can sign for a user, however it happens to be held. */
272
+ interface SshIdentity {
273
+ /** The public key type, e.g. `ssh-ed25519`. */
274
+ readonly keyType: string;
275
+ /** The public key, in the wire format the host expects. */
276
+ readonly keyBlob: Uint8Array;
277
+ /** Where it came from, for logs and error messages. */
278
+ readonly comment: string;
279
+ /**
280
+ * Signature algorithms to offer, best first. RSA keys sign under several,
281
+ * and a host will say which of them it is willing to take.
282
+ */
283
+ readonly algorithms: readonly string[];
284
+ /** The full signature blob (algorithm name and signature) over `data`. */
285
+ sign(data: Uint8Array, algorithm: string): Promise<Uint8Array>;
286
+ }
287
+ /**
288
+ * Read a private key file.
289
+ *
290
+ * Returns one identity, or throws with the reason a human can act on -- which
291
+ * for the common failure is "this key has a passphrase, so let ssh-agent hold
292
+ * it" rather than a decoder error.
293
+ */
294
+ declare function parsePrivateKey(text: string, comment?: string): Promise<SshIdentity>;
248
295
  export declare const DEFAULT_PORT = 22;
249
296
  export declare const COMMAND_MAX_MS: number;
250
297
  /**
@@ -253,9 +300,88 @@ export declare const COMMAND_MAX_MS: number;
253
300
  * Each shell gets its own, because none of the three agree on how to print a
254
301
  * string, run a command and read back its exit status.
255
302
  */
256
- export declare function frameCommand(family: any, command: string, id: any): string;
303
+ export declare function frameCommand(family: string, command: string, id: string): string;
304
+ /** What runCommand resolves with. */
305
+ export interface CommandResult {
306
+ output: string;
307
+ exitStatus: number | null;
308
+ exitSignal?: string;
309
+ timedOut?: boolean;
310
+ started?: boolean;
311
+ reason?: string;
312
+ durationMs: number;
313
+ }
314
+ /** A tool command's output framing, hidden from the screen while it runs. */
315
+ interface DisplayFilter {
316
+ begin: string;
317
+ end: string;
318
+ stage: 'before' | 'inside' | 'closing';
319
+ pending: string;
320
+ }
321
+ /** What a caller can ask of an SSH session. */
322
+ export interface SshSessionOptions {
323
+ username?: string;
324
+ password?: string;
325
+ identities?: SshIdentity[] | (() => Promise<SshIdentity[]>);
326
+ preferredShell?: 'powershell';
327
+ /** Run a subsystem (e.g. 'sftp') rather than a shell. */
328
+ subsystem?: string;
329
+ columns?: number;
330
+ rows?: number;
331
+ hostLabel?: string;
332
+ log?: (step: string, detail?: unknown) => void;
333
+ verifyHost?: (info: any) => Promise<boolean> | boolean;
334
+ openTransport?: ((url: string) => any) | null;
335
+ requestInput?: (prompt: any) => Promise<string>;
336
+ }
257
337
  export declare class SshSession extends EventTarget {
258
338
  #private;
339
+ private readonly options;
340
+ private readonly username;
341
+ private password;
342
+ /**
343
+ * Keys to sign in with, if any. A function rather than a list is allowed
344
+ * because reading keys off a disk -- or asking an agent for them -- is work
345
+ * a session that never needs them should not do.
346
+ */
347
+ /** Replaced with the resolved list the first time it is read. */
348
+ private identities;
349
+ private readonly requestInput;
350
+ /** Whether there is anybody to ask; see the constructor. */
351
+ private readonly canAsk;
352
+ private columns;
353
+ private rows;
354
+ /** A subsystem to run instead of a shell, e.g. 'sftp'. */
355
+ private readonly subsystem;
356
+ private channelId;
357
+ private remoteChannelId;
358
+ private remoteWindow;
359
+ private remoteMaxPacket;
360
+ private localWindow;
361
+ private pendingWrites;
362
+ private shellOpen;
363
+ private commandInFlight;
364
+ /** Set while a tool command runs, to keep its framing off the screen. */
365
+ displayFilter: DisplayFilter | null;
366
+ /** True only after the requested interactive shell is ready for commands. */
367
+ private readySignaled;
368
+ private powerShellAttempted;
369
+ private powerShellTimer;
370
+ /** '', 'posix', 'cmd' or 'powershell'; learned from the prompt. */
371
+ private shellFamily;
372
+ private promptTail;
373
+ private exitStatus;
374
+ private exitSignal?;
375
+ private authMethods;
376
+ /** Which keys were offered and refused, for the message if nothing works. */
377
+ private triedKeys;
378
+ /** Set once the host has said no to the stored password. */
379
+ private passwordRejected;
380
+ private closed;
381
+ private waiters;
382
+ private readonly transport;
383
+ private fingerprint?;
384
+ private lastMessage?;
259
385
  /**
260
386
  * @param {string} url gateway WebSocket URL
261
387
  * @param {object} options
@@ -268,11 +394,11 @@ export declare class SshSession extends EventTarget {
268
394
  * @param {(info: object) => Promise<boolean>} [options.verifyHost]
269
395
  * @param {(prompt: object) => Promise<string>} [options.requestInput]
270
396
  */
271
- constructor(url: string, options?: {});
397
+ constructor(url: string, options?: SshSessionOptions);
272
398
  connect(): void;
273
399
  disconnect(): void;
274
400
  /** Send keystrokes, or anything else the terminal produces, to the shell. */
275
- write(data: Uint8Array): void;
401
+ write(data: Uint8Array | string): void;
276
402
  /**
277
403
  * Run one command in this shell and return what it printed.
278
404
  *
@@ -299,12 +425,20 @@ export declare class SshSession extends EventTarget {
299
425
  /** Tell the shell its window changed, so full-screen programs redraw. */
300
426
  resize(columns: number, rows: number): void;
301
427
  }
428
+ import { Terminal } from '@xterm/xterm';
302
429
  export declare class SshTerminal {
303
- constructor(container: any);
304
- get columns(): any;
305
- get rows(): any;
306
- onData(handler: any): void;
307
- onResize(handler: any): void;
430
+ private readonly container;
431
+ readonly terminal: Terminal;
432
+ private readonly fitAddon;
433
+ private readonly observer;
434
+ /** Where keystrokes go; readLine swaps it out while it owns the line. */
435
+ private dataHandler;
436
+ private fitting;
437
+ constructor(container: HTMLElement);
438
+ get columns(): number;
439
+ get rows(): number;
440
+ onData(handler: ((data: string) => void) | null): void;
441
+ onResize(handler: (cols: number, rows: number) => void): void;
308
442
  write(data: Uint8Array): void;
309
443
  /** Gateway-side notices, kept visually apart from what the host prints. */
310
444
  notice(text: string, tone?: string): void;
@@ -329,20 +463,20 @@ export declare class SshTerminal {
329
463
  fit(): void;
330
464
  /** The grid the host is being told about, for a status line or a log. */
331
465
  get geometry(): {
332
- columns: any;
333
- rows: any;
334
- fontSize: any;
466
+ columns: number;
467
+ rows: number;
468
+ fontSize: number;
335
469
  };
336
- get selection(): any;
470
+ get selection(): string;
337
471
  /**
338
472
  * Read one line from the user, for a password or a one-time code the host
339
473
  * asked for during authentication. The shell is not open yet, so the
340
474
  * terminal is free to be a prompt.
341
475
  */
342
476
  readLine({ prompt, echo }?: {
343
- echo?: boolean;
344
477
  prompt?: string;
345
- }): Promise<unknown>;
478
+ echo?: boolean;
479
+ }): Promise<string>;
346
480
  destroy(): void;
347
481
  }
348
482
  export declare const DRIVERS: Record<RemoteProtocol, ProtocolDriver>;