sigrank 0.17.3 → 0.17.5

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 (3) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/tui.mjs +58 -8
package/README.md CHANGED
@@ -147,7 +147,7 @@ smithery tool call sigrank rank_paste '{"text": "1000000 500000 50000 800000"}'
147
147
  ## Commands
148
148
 
149
149
  ```
150
- ⊙ SigRank CLI v0.17.3
150
+ ⊙ SigRank CLI v0.17.5
151
151
 
152
152
  Default (no args)
153
153
  sigrank unified dashboard: cascade + token pillars + board
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigrank",
3
- "version": "0.17.3",
3
+ "version": "0.17.5",
4
4
  "author": "SunrisesIllNeverSee (https://github.com/SunrisesIllNeverSee)",
5
5
  "description": "SigRank MCP server — the yield cascade + live leaderboard as MCP tools any agent can call",
6
6
  "mcpName": "io.github.SunrisesIllNeverSee/sigrank-mcp",
package/tui.mjs CHANGED
@@ -2136,8 +2136,17 @@ export async function runTui({
2136
2136
  process.stdin.resume();
2137
2137
  process.stdin.setEncoding("utf8");
2138
2138
 
2139
+ // Esc-sequence buffer: some terminals (SSH, tmux, certain emulators) split an
2140
+ // arrow key (\x1b[C) into two data events: \x1b then [C. Without buffering, the
2141
+ // lone \x1b triggers the Esc handler (clearing the code buffer / leaving the tab)
2142
+ // before the [C arrives. We hold \x1b for 25ms; if more bytes follow, we
2143
+ // reassemble and deliver the full sequence. If nothing follows, it's a real Esc.
2144
+ let escBuf = null;
2145
+ let escTimer = null;
2146
+ const ESC_HOLD_MS = 25;
2147
+
2139
2148
  await new Promise((resolve) => {
2140
- process.stdin.on("data", async (key) => {
2149
+ const handleKey = async (key) => {
2141
2150
  const k = key.toLowerCase();
2142
2151
 
2143
2152
  if (k === "q" || k === "\x03") {
@@ -2183,9 +2192,11 @@ export async function runTui({
2183
2192
  "sign-in is temporarily unavailable — try again shortly.",
2184
2193
  };
2185
2194
  connectMsg = `${red("✗")} ${reasons[out.reason] || `sign-in failed (${out.reason || "unknown"}).`}`;
2195
+ codeBuf = ""; // auto-clear on failure — the code is consumed/invalid
2186
2196
  }
2187
2197
  } catch (e) {
2188
2198
  connectMsg = `${red("✗")} ${e.message}`;
2199
+ codeBuf = ""; // auto-clear on error too
2189
2200
  }
2190
2201
  await redraw();
2191
2202
  return;
@@ -2216,13 +2227,13 @@ export async function runTui({
2216
2227
  await redraw();
2217
2228
  return;
2218
2229
  } // backspace
2219
- // [X] sign out / reset — when the code field is empty (so an 'x' typed mid-code
2220
- // still feeds the buffer). Drops the local credential so a server-revoked OR
2221
- // server-enrolled-but-locally-unbound device stops holding a stale device_id,
2222
- // and the next enroll provisions a fresh device_id (escapes the Frankenstein-
2223
- // identity / 409-re-enroll deadlock). Works whether or not isSignedIn() —
2224
- // otherwise a device enrolled server-side but unbound locally is un-escapable
2225
- // (can't sign in: device_already_enrolled; can't sign out: X gated off).
2230
+ // [X] sign out / reset — only when the code field is empty (so 'x'/'X' typed
2231
+ // mid-code still feeds the buffer — codes can contain X). The auto-clear on
2232
+ // sign-in failure (above) ensures the buffer is empty after a failed attempt,
2233
+ // so X works immediately when the user wants to reset. Drops the local
2234
+ // credential so a server-revoked OR server-enrolled-but-locally-unbound device
2235
+ // stops holding a stale device_id, and the next enroll provisions a fresh
2236
+ // device_id (escapes the Frankenstein-identity / 409-re-enroll deadlock).
2226
2237
  if (k === "x" && !codeBuf) {
2227
2238
  const wasSignedIn = isSignedIn(loadIdentity());
2228
2239
  clearIdentity();
@@ -2589,6 +2600,45 @@ export async function runTui({
2589
2600
 
2590
2601
  if (switched) submitMsg = ""; // submit result is tied to the tab it was sent from
2591
2602
  if (switched || k === "w") await redraw();
2603
+ };
2604
+
2605
+ // Buffered stdin listener: reassembles split Esc sequences before dispatch.
2606
+ process.stdin.on("data", (chunk) => {
2607
+ // If we have a pending \x1b held from a previous chunk, combine it with
2608
+ // this chunk to reassemble the full escape sequence (\x1b + [C = \x1b[C).
2609
+ if (escBuf) {
2610
+ clearTimeout(escTimer);
2611
+ const combined = escBuf + chunk;
2612
+ escBuf = null;
2613
+ escTimer = null;
2614
+ // If the combined result is \x1b followed by more \x1b (rare), just
2615
+ // deliver the first as Esc and re-buffer the second. Otherwise deliver
2616
+ // the combined sequence.
2617
+ if (combined.length > 1) {
2618
+ handleKey(combined);
2619
+ return;
2620
+ }
2621
+ // combined is still just \x1b (chunk was empty?) → fall through to hold
2622
+ }
2623
+
2624
+ // Fast path: chunk doesn't start with \x1b → deliver immediately.
2625
+ if (chunk[0] !== "\x1b") {
2626
+ handleKey(chunk);
2627
+ return;
2628
+ }
2629
+ // Chunk starts with \x1b and is longer than 1 byte → complete escape
2630
+ // sequence → deliver immediately.
2631
+ if (chunk.length > 1) {
2632
+ handleKey(chunk);
2633
+ return;
2634
+ }
2635
+ // Chunk is exactly \x1b → hold for ESC_HOLD_MS to see if more bytes follow.
2636
+ escBuf = chunk;
2637
+ escTimer = setTimeout(() => {
2638
+ handleKey(escBuf);
2639
+ escBuf = null;
2640
+ escTimer = null;
2641
+ }, ESC_HOLD_MS);
2592
2642
  });
2593
2643
  });
2594
2644