@cortexkit/common-auth 0.10.0 → 0.11.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.
@@ -12,8 +12,10 @@ export declare const ANSI: {
12
12
  readonly dim: '\u001B[2m';
13
13
  readonly reset: '\u001B[0m';
14
14
  };
15
- export type KeyAction = 'up' | 'down' | 'enter' | 'escape' | 'escape-start' | null;
16
- /** Convert terminal key bytes into the small action set the menu accepts. */
15
+ export type KeyAction = 'up' | 'down' | 'left' | 'right' | 'enter' | 'escape' | 'escape-start' | 'char' | null;
16
+ /** Tokenize complete keys in one read; incomplete escape sequences are ignored. */
17
+ export declare function parseKeys(data: Buffer | string): KeyAction[];
18
+ /** Preserve the single-key parser for callers that only need the first token. */
17
19
  export declare function parseKey(data: Buffer | string): KeyAction;
18
20
  /** Remove colour codes, leaving the text an operator sees. */
19
21
  export declare function stripAnsi(input: string): string;
@@ -12,20 +12,51 @@ export const ANSI = {
12
12
  dim: '\x1b[2m',
13
13
  reset: '\x1b[0m',
14
14
  };
15
- /** Convert terminal key bytes into the small action set the menu accepts. */
16
- export function parseKey(data) {
15
+ /** Tokenize complete keys in one read; incomplete escape sequences are ignored. */
16
+ export function parseKeys(data) {
17
17
  const value = data.toString();
18
- if (value === '\x1b[A' || value === '\x1bOA')
19
- return 'up';
20
- if (value === '\x1b[B' || value === '\x1bOB')
21
- return 'down';
22
- if (value === '\r' || value === '\n')
23
- return 'enter';
24
- if (value === '\x03')
25
- return 'escape';
26
- if (value === '\x1b')
27
- return 'escape-start';
28
- return null;
18
+ const keys = [];
19
+ for (let index = 0; index < value.length;) {
20
+ const char = value[index];
21
+ if (char === '\x1b') {
22
+ const sequence = value.slice(index, index + 3);
23
+ const arrow = sequence[1] === '[' || sequence[1] === 'O' ? sequence[2] : undefined;
24
+ if (arrow && 'ABCD'.includes(arrow)) {
25
+ const action = { A: 'up', B: 'down', C: 'right', D: 'left' };
26
+ const parsed = action[arrow];
27
+ if (parsed)
28
+ keys.push(parsed);
29
+ index += 3;
30
+ }
31
+ else if (index + 1 === value.length) {
32
+ // Only a chunk consisting solely of ESC gets the legacy Escape timeout.
33
+ if (index === 0)
34
+ keys.push('escape-start');
35
+ index += 1;
36
+ }
37
+ else if ((value[index + 1] === '[' || value[index + 1] === 'O') &&
38
+ index + 2 >= value.length) {
39
+ // An incomplete escape prefix at the chunk boundary is not a key.
40
+ break;
41
+ }
42
+ else {
43
+ index += 1;
44
+ }
45
+ continue;
46
+ }
47
+ if (char === '\r' || char === '\n')
48
+ keys.push('enter');
49
+ else if (char === '\x03')
50
+ keys.push('escape');
51
+ else if (char && char >= ' ')
52
+ keys.push('char');
53
+ index += 1;
54
+ }
55
+ return keys;
56
+ }
57
+ /** Preserve the single-key parser for callers that only need the first token. */
58
+ export function parseKey(data) {
59
+ return parseKeys(data)[0] ?? null;
29
60
  }
30
61
  const ANSI_PATTERN = `${String.fromCharCode(27)}\\[[0-9;]*m`;
31
62
  const ANSI_REGEX = new RegExp(ANSI_PATTERN, 'g');
@@ -1,7 +1,7 @@
1
1
  export type { AccountMenuOptions } from './accounts.js';
2
2
  export { accountMenuActions, addAccountAction, checkQuotasAction, deleteAllAction, listAccountsAction, MENU_DISABLE_REASON, poolHasCredential, quotaLines, reauthenticateAction, removeAccountAction, runAccountMenu, toggleAccountAction, } from './accounts.js';
3
3
  export type { KeyAction } from './ansi.js';
4
- export { ANSI, parseKey, stripAnsi, truncateAnsi } from './ansi.js';
4
+ export { ANSI, parseKey, parseKeys, stripAnsi, truncateAnsi } from './ansi.js';
5
5
  export { confirm } from './confirm.js';
6
6
  export type { DoctorActionOptions, DoctorCheck, DoctorFinding, DoctorRepair, DoctorReport, RepairOutcome, } from './doctor.js';
7
7
  export { applyChosenRepairs, DOCTOR_CHECK_FAILED, doctorAction, formatDoctorReport, runDoctorChecks, } from './doctor.js';
@@ -2,7 +2,7 @@
2
2
  // runtime, the OpenCode v1 `authorize` contract around it, and the account
3
3
  // actions built over the `/store` pool.
4
4
  export { accountMenuActions, addAccountAction, checkQuotasAction, deleteAllAction, listAccountsAction, MENU_DISABLE_REASON, poolHasCredential, quotaLines, reauthenticateAction, removeAccountAction, runAccountMenu, toggleAccountAction, } from './accounts.js';
5
- export { ANSI, parseKey, stripAnsi, truncateAnsi } from './ansi.js';
5
+ export { ANSI, parseKey, parseKeys, stripAnsi, truncateAnsi } from './ansi.js';
6
6
  export { confirm } from './confirm.js';
7
7
  export { applyChosenRepairs, DOCTOR_CHECK_FAILED, doctorAction, formatDoctorReport, runDoctorChecks, } from './doctor.js';
8
8
  export { openBrowserForMenu, runMenuLogin } from './login.js';
@@ -1,4 +1,4 @@
1
- import { ANSI, parseKey, truncateAnsi } from './ansi.js';
1
+ import { ANSI, parseKeys, truncateAnsi } from './ansi.js';
2
2
  import { isInteractive } from './terminal.js';
3
3
  /** How long a lone Escape byte waits for the rest of an arrow-key sequence. */
4
4
  const ESCAPE_TIMEOUT_MS = 50;
@@ -109,30 +109,36 @@ export async function select(terminal, items, options) {
109
109
  };
110
110
  const onSignal = () => finish(null);
111
111
  const onKey = (data) => {
112
- if (escapeTimeout) {
113
- clearTimeout(escapeTimeout);
114
- escapeTimeout = null;
115
- }
116
- switch (parseKey(data)) {
117
- case 'up':
118
- cursor = (cursor - 1 + items.length) % items.length;
119
- render();
120
- break;
121
- case 'down':
122
- cursor = (cursor + 1) % items.length;
123
- render();
124
- break;
125
- case 'enter':
126
- finish(items[cursor]?.value ?? null);
127
- break;
128
- case 'escape':
129
- finish(null);
130
- break;
131
- case 'escape-start':
132
- // A bare Escape byte is also the start of an arrow-key sequence
133
- // that may arrive split across reads; only a lone one cancels.
134
- escapeTimeout = setTimeout(() => finish(null), ESCAPE_TIMEOUT_MS);
112
+ for (const key of parseKeys(data)) {
113
+ if (cleaned)
135
114
  break;
115
+ if (escapeTimeout) {
116
+ clearTimeout(escapeTimeout);
117
+ escapeTimeout = null;
118
+ }
119
+ switch (key) {
120
+ case 'up':
121
+ cursor = (cursor - 1 + items.length) % items.length;
122
+ render();
123
+ break;
124
+ case 'down':
125
+ cursor = (cursor + 1) % items.length;
126
+ render();
127
+ break;
128
+ case 'enter':
129
+ finish(items[cursor]?.value ?? null);
130
+ break;
131
+ case 'escape':
132
+ finish(null);
133
+ break;
134
+ case 'escape-start':
135
+ // A bare Escape byte is also the start of an arrow-key sequence
136
+ // that may arrive split across reads; only a lone one cancels.
137
+ escapeTimeout = setTimeout(() => finish(null), ESCAPE_TIMEOUT_MS);
138
+ break;
139
+ default:
140
+ break;
141
+ }
136
142
  }
137
143
  };
138
144
  signals?.once('SIGINT', onSignal);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/common-auth",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "Shared code for the CortexKit auth plugins: account pool, quota and routing, commands and auth menu, OpenCode 2 hooks, Claustrum custody, and plumbing (loopback RPC, file locks, logger, sidebar state, TUI preferences and build).",
5
5
  "license": "MIT",
6
6
  "repository": {