@pikaa-ai/pikaa 0.3.15 → 0.3.18

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 +104 -78
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -6429,7 +6429,7 @@ function parsePatch(oldSrc, newSrc, contextLines = 3) {
6429
6429
  // package.json
6430
6430
  var package_default = {
6431
6431
  name: "@pikaa-ai/pikaa",
6432
- version: "0.3.15",
6432
+ version: "0.3.18",
6433
6433
  description: "PIKAA CLI - AI coding agent that runs locally in your terminal.",
6434
6434
  main: "./dist/index.js",
6435
6435
  module: "./dist/index.js",
@@ -6986,12 +6986,23 @@ ${preview}${more}`);
6986
6986
  }
6987
6987
 
6988
6988
  // src/cli/ui/line-editor.ts
6989
+ import readline2 from "readline";
6990
+
6991
+ // src/cli/ui/keypress.ts
6989
6992
  import readline from "readline";
6990
6993
  var keypressEventsInitialized = false;
6991
- function ensureKeypressInitialized() {
6994
+ var listeners = new Set;
6995
+ function initGlobalKeypress() {
6992
6996
  if (!keypressEventsInitialized && process.stdin.isTTY) {
6993
6997
  readline.emitKeypressEvents(process.stdin);
6994
6998
  keypressEventsInitialized = true;
6999
+ process.stdin.on("keypress", (str, key) => {
7000
+ for (const listener of Array.from(listeners)) {
7001
+ try {
7002
+ listener(str, key);
7003
+ } catch {}
7004
+ }
7005
+ });
6995
7006
  process.on("exit", () => {
6996
7007
  if (process.stdin.isTTY) {
6997
7008
  try {
@@ -7001,7 +7012,15 @@ function ensureKeypressInitialized() {
7001
7012
  });
7002
7013
  }
7003
7014
  }
7015
+ function addGlobalKeypressListener(listener) {
7016
+ initGlobalKeypress();
7017
+ listeners.add(listener);
7018
+ return () => {
7019
+ listeners.delete(listener);
7020
+ };
7021
+ }
7004
7022
 
7023
+ // src/cli/ui/line-editor.ts
7005
7024
  class InteractiveLineEditor {
7006
7025
  promptSymbol;
7007
7026
  cwd;
@@ -7052,21 +7071,21 @@ class InteractiveLineEditor {
7052
7071
  async readLine() {
7053
7072
  if (!process.stdin.isTTY) {
7054
7073
  return new Promise((resolve17) => {
7055
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
7074
+ const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
7056
7075
  rl.question(this.promptSymbol, (answer) => {
7057
7076
  rl.close();
7058
7077
  resolve17(answer);
7059
7078
  });
7060
7079
  });
7061
7080
  }
7062
- ensureKeypressInitialized();
7063
7081
  return new Promise((resolve17) => {
7064
7082
  let buffer = "";
7065
7083
  let cursor = 0;
7066
7084
  let selectedIndex = 0;
7067
7085
  let scrollTop = 0;
7068
- let renderedMenuLines = 0;
7069
7086
  let popupDismissed = false;
7087
+ let lastCursorRowFromTop = 0;
7088
+ let unsubscribeKeypress = null;
7070
7089
  if (process.stdin.isTTY) {
7071
7090
  try {
7072
7091
  process.stdin.setRawMode(true);
@@ -7129,20 +7148,21 @@ class InteractiveLineEditor {
7129
7148
  if (scrollTop > maxScroll)
7130
7149
  scrollTop = maxScroll;
7131
7150
  };
7132
- const clearMenu = () => {
7133
- if (renderedMenuLines > 0) {
7134
- process.stdout.write("\x1B[J");
7135
- renderedMenuLines = 0;
7136
- }
7137
- };
7138
7151
  const redraw = () => {
7139
- process.stdout.write("\x1B[J");
7140
- process.stdout.write(`\r\x1B[2K${this.promptSymbol}${buffer}`);
7152
+ const termCols = getTerminalCols();
7153
+ const visiblePromptWidth = this.promptSymbol.replace(/\x1b\[[0-9;]*m/g, "").length;
7154
+ if (lastCursorRowFromTop > 0) {
7155
+ process.stdout.write(`\x1B[${lastCursorRowFromTop}A`);
7156
+ }
7157
+ process.stdout.write("\r\x1B[J");
7158
+ process.stdout.write(`${this.promptSymbol}${buffer}`);
7159
+ const totalPromptChars = visiblePromptWidth + buffer.length;
7160
+ const promptRows = Math.max(1, Math.floor(totalPromptChars / termCols) + 1);
7141
7161
  const slashMatches = getMatchingCommands();
7142
7162
  const activeFile = getActiveFileQuery();
7143
7163
  const fileMatches = activeFile ? getMatchingFiles(activeFile.query) : [];
7164
+ let menuRows = 0;
7144
7165
  if (buffer.startsWith("/") && !popupDismissed && slashMatches.length > 0) {
7145
- const termCols = getTerminalCols();
7146
7166
  const BOX_WIDTH = Math.max(20, Math.min(termCols - 4, 120));
7147
7167
  const maxVisible = Math.min(slashMatches.length, 7);
7148
7168
  ensureVisible(slashMatches.length, maxVisible);
@@ -7173,10 +7193,8 @@ class InteractiveLineEditor {
7173
7193
  process.stdout.write(`
7174
7194
  \x1B[2K${line}`);
7175
7195
  }
7176
- renderedMenuLines = menuLines.length;
7177
- process.stdout.write(`\x1B[${renderedMenuLines}A`);
7196
+ menuRows = menuLines.length;
7178
7197
  } else if (activeFile && !popupDismissed && fileMatches.length > 0) {
7179
- const termCols = getTerminalCols();
7180
7198
  const BOX_WIDTH = Math.max(20, Math.min(termCols - 4, 120));
7181
7199
  const maxVisible = Math.min(fileMatches.length, 7);
7182
7200
  ensureVisible(fileMatches.length, maxVisible);
@@ -7212,8 +7230,7 @@ class InteractiveLineEditor {
7212
7230
  process.stdout.write(`
7213
7231
  \x1B[2K${line}`);
7214
7232
  }
7215
- renderedMenuLines = menuLines.length;
7216
- process.stdout.write(`\x1B[${renderedMenuLines}A`);
7233
+ menuRows = menuLines.length;
7217
7234
  } else {
7218
7235
  const RULE_COLOR2 = "\x1B[38;2;60;60;68m";
7219
7236
  const bottomRule = ` ${RULE_COLOR2}${getRule()}\x1B[0m`;
@@ -7221,16 +7238,20 @@ class InteractiveLineEditor {
7221
7238
  process.stdout.write(`
7222
7239
  \x1B[2K${bottomRule}
7223
7240
  \x1B[2K${modeLine}`);
7224
- renderedMenuLines = 2;
7225
- process.stdout.write(`\x1B[2A`);
7241
+ menuRows = 2;
7242
+ }
7243
+ const cursorCharsFromTop = visiblePromptWidth + cursor;
7244
+ const cursorRow = Math.floor(cursorCharsFromTop / termCols);
7245
+ const cursorCol = cursorCharsFromTop % termCols;
7246
+ const moveUpRows = promptRows - 1 - cursorRow + menuRows;
7247
+ if (moveUpRows > 0) {
7248
+ process.stdout.write(`\x1B[${moveUpRows}A`);
7226
7249
  }
7227
- const visiblePromptLength = this.promptSymbol.replace(/\x1b\[[0-9;]*m/g, "").length;
7228
- const cursorCol = visiblePromptLength + cursor;
7250
+ process.stdout.write("\r");
7229
7251
  if (cursorCol > 0) {
7230
- process.stdout.write(`\r\x1B[${cursorCol}C`);
7231
- } else {
7232
- process.stdout.write("\r");
7252
+ process.stdout.write(`\x1B[${cursorCol}C`);
7233
7253
  }
7254
+ lastCursorRowFromTop = cursorRow;
7234
7255
  };
7235
7256
  const onResize = () => {
7236
7257
  redraw();
@@ -7242,19 +7263,25 @@ class InteractiveLineEditor {
7242
7263
  if (process.stdout && typeof process.stdout.removeListener === "function") {
7243
7264
  process.stdout.removeListener("resize", onResize);
7244
7265
  }
7245
- clearMenu();
7246
- process.stdin.removeListener("keypress", onKeypress);
7266
+ if (unsubscribeKeypress) {
7267
+ unsubscribeKeypress();
7268
+ unsubscribeKeypress = null;
7269
+ }
7247
7270
  if (process.stdin.isTTY) {
7248
7271
  try {
7249
7272
  process.stdin.setRawMode(false);
7250
7273
  } catch {}
7251
7274
  }
7275
+ if (lastCursorRowFromTop > 0) {
7276
+ process.stdout.write(`\x1B[${lastCursorRowFromTop}A`);
7277
+ }
7278
+ process.stdout.write("\r\x1B[J");
7252
7279
  if (result.trim().length > 0 && !result.startsWith("/")) {
7253
- process.stdout.write(`\x1B[1A\r\x1B[2K\x1B[J${CliFormatter.formatClaudeUserPrompt(result)}
7280
+ process.stdout.write(`${CliFormatter.formatClaudeUserPrompt(result)}
7254
7281
 
7255
7282
  `);
7256
7283
  } else {
7257
- process.stdout.write(`\x1B[1A\r\x1B[2K\x1B[J
7284
+ process.stdout.write(`
7258
7285
  `);
7259
7286
  }
7260
7287
  resolve17(result);
@@ -7275,13 +7302,19 @@ class InteractiveLineEditor {
7275
7302
  if (process.stdout && typeof process.stdout.removeListener === "function") {
7276
7303
  process.stdout.removeListener("resize", onResize);
7277
7304
  }
7278
- clearMenu();
7305
+ if (lastCursorRowFromTop > 0) {
7306
+ process.stdout.write(`\x1B[${lastCursorRowFromTop}A`);
7307
+ }
7308
+ process.stdout.write("\r\x1B[J");
7309
+ if (unsubscribeKeypress) {
7310
+ unsubscribeKeypress();
7311
+ unsubscribeKeypress = null;
7312
+ }
7279
7313
  if (process.stdin.isTTY) {
7280
7314
  try {
7281
7315
  process.stdin.setRawMode(false);
7282
7316
  } catch {}
7283
7317
  }
7284
- process.stdin.removeListener("keypress", onKeypress);
7285
7318
  if (this.onInterrupt) {
7286
7319
  this.onInterrupt();
7287
7320
  }
@@ -7290,12 +7323,9 @@ class InteractiveLineEditor {
7290
7323
  process.exit(0);
7291
7324
  }
7292
7325
  if (key.name === "escape") {
7293
- if (renderedMenuLines > 0) {
7294
- popupDismissed = true;
7295
- clearMenu();
7296
- redraw();
7297
- return;
7298
- }
7326
+ popupDismissed = true;
7327
+ redraw();
7328
+ return;
7299
7329
  }
7300
7330
  if (key.ctrl && key.name === "d") {
7301
7331
  cleanupAndResolve("/exit");
@@ -7423,7 +7453,7 @@ class InteractiveLineEditor {
7423
7453
  redraw();
7424
7454
  }
7425
7455
  };
7426
- process.stdin.on("keypress", onKeypress);
7456
+ unsubscribeKeypress = addGlobalKeypressListener(onKeypress);
7427
7457
  const RULE_COLOR = "\x1B[38;2;60;60;68m";
7428
7458
  process.stdout.write(`
7429
7459
  ${RULE_COLOR}${getRule()}\x1B[0m
@@ -7434,31 +7464,27 @@ class InteractiveLineEditor {
7434
7464
  }
7435
7465
 
7436
7466
  // src/cli/ui/prompt.ts
7437
- import readline2 from "readline";
7467
+ import readline3 from "readline";
7438
7468
  async function promptChoice(config) {
7439
7469
  const { message, choices, defaultIndex = 0 } = config;
7440
7470
  if (!process.stdin.isTTY) {
7441
7471
  if (!process.stdin.readable) {
7442
- return choices[defaultIndex]?.value ?? choices[0].value;
7472
+ const def = choices[defaultIndex] || choices[0];
7473
+ return def ? def.value : "";
7443
7474
  }
7444
7475
  return new Promise((resolve17) => {
7445
- const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
7446
- const hint = choices.map((c3) => c3.isDefault ? `[${c3.key.toUpperCase()}]` : `[${c3.key}]`).join("/");
7447
- let resolved = false;
7448
- const doResolve = (val) => {
7449
- if (!resolved) {
7450
- resolved = true;
7451
- rl.close();
7452
- resolve17(val);
7453
- }
7454
- };
7455
- rl.question(`${message} ${hint}: `, (answer) => {
7476
+ const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
7477
+ const promptText = ` ${message} (${choices.map((c3) => `[${c3.key}] ${c3.label}`).join(", ")}): `;
7478
+ rl.question(promptText, (answer) => {
7479
+ rl.close();
7456
7480
  const trimmed = answer.trim().toLowerCase();
7457
- const matched = choices.find((c3) => c3.key.toLowerCase() === trimmed);
7458
- doResolve(matched ? matched.value : choices[defaultIndex].value);
7459
- });
7460
- rl.on("close", () => {
7461
- doResolve(choices[defaultIndex].value);
7481
+ const match = choices.find((c3) => c3.key.toLowerCase() === trimmed);
7482
+ if (match) {
7483
+ resolve17(match.value);
7484
+ } else {
7485
+ const def = choices[defaultIndex] || choices[0];
7486
+ resolve17(def ? def.value : "");
7487
+ }
7462
7488
  });
7463
7489
  });
7464
7490
  }
@@ -7466,7 +7492,6 @@ async function promptChoice(config) {
7466
7492
  let selectedIndex = defaultIndex;
7467
7493
  if (selectedIndex < 0 || selectedIndex >= choices.length)
7468
7494
  selectedIndex = 0;
7469
- readline2.emitKeypressEvents(process.stdin);
7470
7495
  const wasRaw = process.stdin.isRaw;
7471
7496
  try {
7472
7497
  process.stdin.setRawMode(true);
@@ -7484,8 +7509,12 @@ async function promptChoice(config) {
7484
7509
  process.stdout.write(`\r\x1B[2K ${style.bold(message)}
7485
7510
  \r\x1B[2K${parts.join(" ")}`);
7486
7511
  };
7512
+ let unsubscribe = null;
7487
7513
  const cleanup = (confirmedChoice) => {
7488
- process.stdin.removeListener("keypress", onKeypress);
7514
+ if (unsubscribe) {
7515
+ unsubscribe();
7516
+ unsubscribe = null;
7517
+ }
7489
7518
  try {
7490
7519
  process.stdin.setRawMode(wasRaw ?? false);
7491
7520
  } catch {}
@@ -7497,21 +7526,12 @@ async function promptChoice(config) {
7497
7526
  if (!key)
7498
7527
  return;
7499
7528
  if (key.ctrl && key.name === "c") {
7500
- cleanup(choices.find((c3) => c3.key.toLowerCase() === "n") || choices[defaultIndex]);
7529
+ const def = choices[defaultIndex] || choices[0];
7530
+ cleanup(def);
7501
7531
  return;
7502
7532
  }
7503
- if (key.name === "escape") {
7504
- cleanup(choices.find((c3) => c3.key.toLowerCase() === "n") || choices[defaultIndex]);
7505
- return;
7506
- }
7507
- if (key.name === "return" || key.name === "enter") {
7508
- cleanup(choices[selectedIndex]);
7509
- return;
7510
- }
7511
- if (key.name === "left") {
7533
+ if (key.name === "left" || key.name === "up" || key.name === "h" || key.name === "k") {
7512
7534
  selectedIndex = (selectedIndex - 1 + choices.length) % choices.length;
7513
- process.stdout.write("\x1B[1A");
7514
- render();
7515
7535
  return;
7516
7536
  }
7517
7537
  if (key.name === "right" || key.name === "tab") {
@@ -7568,7 +7588,6 @@ async function promptToolApproval(params) {
7568
7588
  }
7569
7589
  return new Promise((resolve17) => {
7570
7590
  let selectedIndex = 1;
7571
- readline2.emitKeypressEvents(process.stdin);
7572
7591
  const wasRaw = process.stdin.isRaw;
7573
7592
  try {
7574
7593
  process.stdin.setRawMode(true);
@@ -7577,8 +7596,12 @@ async function promptToolApproval(params) {
7577
7596
  const render = () => {
7578
7597
  renderCard(selectedIndex);
7579
7598
  };
7599
+ let unsubscribe = null;
7580
7600
  const cleanup = (val) => {
7581
- process.stdin.removeListener("keypress", onKeypress);
7601
+ if (unsubscribe) {
7602
+ unsubscribe();
7603
+ unsubscribe = null;
7604
+ }
7582
7605
  try {
7583
7606
  process.stdin.setRawMode(wasRaw ?? false);
7584
7607
  } catch {}
@@ -7637,7 +7660,7 @@ async function promptToolApproval(params) {
7637
7660
  return;
7638
7661
  }
7639
7662
  };
7640
- process.stdin.on("keypress", onKeypress);
7663
+ unsubscribe = addGlobalKeypressListener(onKeypress);
7641
7664
  render();
7642
7665
  });
7643
7666
  }
@@ -7667,7 +7690,7 @@ async function promptUserQuestion(params) {
7667
7690
  return options[0] || "yes";
7668
7691
  }
7669
7692
  return new Promise((resolve17) => {
7670
- const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
7693
+ const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
7671
7694
  const promptLabel = options.length > 0 ? `Select [1-${options.length}] or type custom response: ` : `Your response: `;
7672
7695
  rl.question(` ${style.bold(promptLabel)}`, (answer) => {
7673
7696
  rl.close();
@@ -7736,7 +7759,6 @@ async function promptInteractiveList(config) {
7736
7759
  let selectedIndex = Math.max(0, Math.min(defaultIndex, items.length - 1));
7737
7760
  let scrollTop = 0;
7738
7761
  let renderedLines = 0;
7739
- readline2.emitKeypressEvents(process.stdin);
7740
7762
  const wasRaw = process.stdin.isRaw;
7741
7763
  try {
7742
7764
  process.stdin.setRawMode(true);
@@ -7813,6 +7835,7 @@ async function promptInteractiveList(config) {
7813
7835
  process.stdout.write(buffer);
7814
7836
  renderedLines = lines.length;
7815
7837
  };
7838
+ let unsubscribe = null;
7816
7839
  const cleanup = (res) => {
7817
7840
  if (renderedLines > 0) {
7818
7841
  process.stdout.write(`\x1B[${renderedLines}A\r\x1B[J\x1B[?25h`);
@@ -7820,7 +7843,10 @@ async function promptInteractiveList(config) {
7820
7843
  } else {
7821
7844
  process.stdout.write("\x1B[?25h");
7822
7845
  }
7823
- process.stdin.removeListener("keypress", onKeypress);
7846
+ if (unsubscribe) {
7847
+ unsubscribe();
7848
+ unsubscribe = null;
7849
+ }
7824
7850
  try {
7825
7851
  process.stdin.setRawMode(wasRaw ?? false);
7826
7852
  } catch {}
@@ -7889,7 +7915,7 @@ async function promptInteractiveList(config) {
7889
7915
  render();
7890
7916
  }
7891
7917
  };
7892
- process.stdin.on("keypress", onKeypress);
7918
+ unsubscribe = addGlobalKeypressListener(onKeypress);
7893
7919
  render();
7894
7920
  });
7895
7921
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikaa-ai/pikaa",
3
- "version": "0.3.15",
3
+ "version": "0.3.18",
4
4
  "description": "PIKAA CLI - AI coding agent that runs locally in your terminal.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",