pty-manager 1.7.3 → 1.8.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.
package/dist/index.d.mts CHANGED
@@ -669,6 +669,15 @@ declare class PTYSession extends EventEmitter {
669
669
  * @param keys - Key name(s) to send, e.g. "ctrl+c" or ["up", "up", "enter"]
670
670
  */
671
671
  sendKeys(keys: string | string[]): void;
672
+ /**
673
+ * Normalize a list of key names for SPECIAL_KEYS lookup.
674
+ *
675
+ * Handles two problems:
676
+ * 1. Modifier aliases: "control" → "ctrl", "command" → "meta", "option" → "alt"
677
+ * 2. Comma-separated compound keys from stall classifier: ["control", "c"] → ["ctrl+c"]
678
+ * A bare modifier followed by a single char/key is joined with "+".
679
+ */
680
+ static normalizeKeyList(keys: string[]): string[];
672
681
  /**
673
682
  * Select a TUI menu option by index (0-based).
674
683
  * Sends Down arrow `optionIndex` times, then Enter, with 50ms delays.
@@ -1002,6 +1011,11 @@ declare class ShellAdapter implements CLIAdapter {
1002
1011
  detectLogin(_output: string): LoginDetection;
1003
1012
  detectBlockingPrompt(_output: string): BlockingPromptDetection;
1004
1013
  detectReady(output: string): boolean;
1014
+ /**
1015
+ * Detect shell continuation prompts that indicate the shell is NOT ready
1016
+ * for a new command (e.g., unclosed quote, heredoc, backtick).
1017
+ */
1018
+ private isContinuationPrompt;
1005
1019
  detectExit(output: string): {
1006
1020
  exited: boolean;
1007
1021
  code?: number;
package/dist/index.d.ts CHANGED
@@ -669,6 +669,15 @@ declare class PTYSession extends EventEmitter {
669
669
  * @param keys - Key name(s) to send, e.g. "ctrl+c" or ["up", "up", "enter"]
670
670
  */
671
671
  sendKeys(keys: string | string[]): void;
672
+ /**
673
+ * Normalize a list of key names for SPECIAL_KEYS lookup.
674
+ *
675
+ * Handles two problems:
676
+ * 1. Modifier aliases: "control" → "ctrl", "command" → "meta", "option" → "alt"
677
+ * 2. Comma-separated compound keys from stall classifier: ["control", "c"] → ["ctrl+c"]
678
+ * A bare modifier followed by a single char/key is joined with "+".
679
+ */
680
+ static normalizeKeyList(keys: string[]): string[];
672
681
  /**
673
682
  * Select a TUI menu option by index (0-based).
674
683
  * Sends Down arrow `optionIndex` times, then Enter, with 50ms delays.
@@ -1002,6 +1011,11 @@ declare class ShellAdapter implements CLIAdapter {
1002
1011
  detectLogin(_output: string): LoginDetection;
1003
1012
  detectBlockingPrompt(_output: string): BlockingPromptDetection;
1004
1013
  detectReady(output: string): boolean;
1014
+ /**
1015
+ * Detect shell continuation prompts that indicate the shell is NOT ready
1016
+ * for a new command (e.g., unclosed quote, heredoc, backtick).
1017
+ */
1018
+ private isContinuationPrompt;
1005
1019
  detectExit(output: string): {
1006
1020
  exited: boolean;
1007
1021
  code?: number;
package/dist/index.js CHANGED
@@ -1242,24 +1242,71 @@ var PTYSession = class _PTYSession extends import_events.EventEmitter {
1242
1242
  throw new Error("Session not started");
1243
1243
  }
1244
1244
  const keyList = Array.isArray(keys) ? keys : [keys];
1245
+ const normalized = _PTYSession.normalizeKeyList(keyList);
1245
1246
  this._stallEmissionCount = 0;
1246
1247
  this.resetStallTimer();
1247
- for (const key of keyList) {
1248
- const normalizedKey = key.toLowerCase().trim();
1249
- const sequence = SPECIAL_KEYS[normalizedKey];
1248
+ for (const key of normalized) {
1249
+ const sequence = SPECIAL_KEYS[key];
1250
1250
  if (sequence) {
1251
1251
  this._lastActivityAt = /* @__PURE__ */ new Date();
1252
1252
  this.ptyProcess.write(sequence);
1253
- this.logger.debug({ sessionId: this.id, key: normalizedKey }, "Sent special key");
1253
+ this.logger.debug({ sessionId: this.id, key }, "Sent special key");
1254
1254
  } else {
1255
1255
  this.logger.warn(
1256
- { sessionId: this.id, key: normalizedKey },
1256
+ { sessionId: this.id, key },
1257
1257
  "Unknown special key, sending as literal"
1258
1258
  );
1259
1259
  this.ptyProcess.write(key);
1260
1260
  }
1261
1261
  }
1262
1262
  }
1263
+ /**
1264
+ * Normalize a list of key names for SPECIAL_KEYS lookup.
1265
+ *
1266
+ * Handles two problems:
1267
+ * 1. Modifier aliases: "control" → "ctrl", "command" → "meta", "option" → "alt"
1268
+ * 2. Comma-separated compound keys from stall classifier: ["control", "c"] → ["ctrl+c"]
1269
+ * A bare modifier followed by a single char/key is joined with "+".
1270
+ */
1271
+ static normalizeKeyList(keys) {
1272
+ const MODIFIER_MAP = {
1273
+ control: "ctrl",
1274
+ command: "meta",
1275
+ cmd: "meta",
1276
+ option: "alt",
1277
+ opt: "alt"
1278
+ };
1279
+ const MODIFIER_NAMES = /* @__PURE__ */ new Set([
1280
+ "ctrl",
1281
+ "alt",
1282
+ "shift",
1283
+ "meta",
1284
+ // Also match the aliases so we can detect them before remapping
1285
+ ...Object.keys(MODIFIER_MAP)
1286
+ ]);
1287
+ const result = [];
1288
+ let i = 0;
1289
+ while (i < keys.length) {
1290
+ let key = keys[i].toLowerCase().trim();
1291
+ if (MODIFIER_MAP[key]) {
1292
+ key = MODIFIER_MAP[key];
1293
+ }
1294
+ if (MODIFIER_NAMES.has(key) && i + 1 < keys.length) {
1295
+ let nextKey = keys[i + 1].toLowerCase().trim();
1296
+ if (MODIFIER_MAP[nextKey]) {
1297
+ nextKey = MODIFIER_MAP[nextKey];
1298
+ }
1299
+ if (!MODIFIER_NAMES.has(nextKey)) {
1300
+ result.push(`${key}+${nextKey}`);
1301
+ i += 2;
1302
+ continue;
1303
+ }
1304
+ }
1305
+ result.push(key);
1306
+ i++;
1307
+ }
1308
+ return result;
1309
+ }
1263
1310
  /**
1264
1311
  * Select a TUI menu option by index (0-based).
1265
1312
  * Sends Down arrow `optionIndex` times, then Enter, with 50ms delays.
@@ -2261,7 +2308,18 @@ var ShellAdapter = class {
2261
2308
  return { detected: false };
2262
2309
  }
2263
2310
  detectReady(output) {
2264
- return output.includes(this.promptStr) || output.includes("$") || output.length > 10;
2311
+ if (this.isContinuationPrompt(output)) {
2312
+ return false;
2313
+ }
2314
+ return this.getPromptPattern().test(this.stripAnsi(output));
2315
+ }
2316
+ /**
2317
+ * Detect shell continuation prompts that indicate the shell is NOT ready
2318
+ * for a new command (e.g., unclosed quote, heredoc, backtick).
2319
+ */
2320
+ isContinuationPrompt(output) {
2321
+ const stripped = this.stripAnsi(output);
2322
+ return /(?:quote|dquote|heredoc|bquote|cmdsubst|pipe|then|else|do|loop)>\s*$/.test(stripped) || /(?:quote|dquote|heredoc|bquote)>\s*$/m.test(stripped);
2265
2323
  }
2266
2324
  detectExit(output) {
2267
2325
  if (output.includes("exit")) {
@@ -2284,7 +2342,7 @@ var ShellAdapter = class {
2284
2342
  }
2285
2343
  getPromptPattern() {
2286
2344
  const escaped = this.promptStr.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2287
- return new RegExp(`(?:${escaped}|\\$|#|>)\\s*$`, "m");
2345
+ return new RegExp(`(?:${escaped}|\\$|#)\\s*$`, "m");
2288
2346
  }
2289
2347
  async validateInstallation() {
2290
2348
  return { installed: true };
@@ -2380,9 +2438,6 @@ var BunCompatiblePTYManager = class extends import_events3.EventEmitter {
2380
2438
  const id = event.id;
2381
2439
  switch (eventType) {
2382
2440
  case "worker_ready":
2383
- if (this.adapterModules.length > 0) {
2384
- this.sendCommand({ cmd: "registerAdapters", modules: this.adapterModules });
2385
- }
2386
2441
  if (this._stallDetectionEnabled) {
2387
2442
  this.sendCommand({
2388
2443
  cmd: "configureStallDetection",
@@ -2390,9 +2445,23 @@ var BunCompatiblePTYManager = class extends import_events3.EventEmitter {
2390
2445
  timeoutMs: this._stallTimeoutMs
2391
2446
  });
2392
2447
  }
2393
- this.ready = true;
2394
- this.readyResolve();
2395
- this.emit("ready");
2448
+ if (this.adapterModules.length > 0) {
2449
+ this.sendCommand({ cmd: "registerAdapters", modules: this.adapterModules });
2450
+ this.createPending("registerAdapters").then(() => {
2451
+ this.ready = true;
2452
+ this.readyResolve();
2453
+ this.emit("ready");
2454
+ }).catch((err) => {
2455
+ this.emit("worker_error", `Failed to register adapters: ${err}`);
2456
+ this.ready = true;
2457
+ this.readyResolve();
2458
+ this.emit("ready");
2459
+ });
2460
+ } else {
2461
+ this.ready = true;
2462
+ this.readyResolve();
2463
+ this.emit("ready");
2464
+ }
2396
2465
  break;
2397
2466
  case "spawned": {
2398
2467
  const session = {