@prave/cli 1.0.0 → 1.0.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.
- package/dist/commands/whoami.js +11 -0
- package/dist/lib/prompt.js +92 -35
- package/package.json +2 -2
package/dist/commands/whoami.js
CHANGED
|
@@ -17,6 +17,17 @@ export async function whoamiCommand() {
|
|
|
17
17
|
process.exitCode = 1;
|
|
18
18
|
return;
|
|
19
19
|
}
|
|
20
|
+
// Detect creds left over from older CLI versions that predate the
|
|
21
|
+
// refresh-token flow. Without a refresh_token we can't auto-rotate the
|
|
22
|
+
// expired access_token, so the user gets stuck in a "session expired"
|
|
23
|
+
// loop. Direct them to `prave login` immediately rather than letting
|
|
24
|
+
// the API call fail confusingly.
|
|
25
|
+
if (!creds.refresh_token) {
|
|
26
|
+
log.warn('Stored credentials predate the refresh-token flow.');
|
|
27
|
+
log.dim('Run `prave login` once to upgrade — refreshes will be automatic afterwards.');
|
|
28
|
+
process.exitCode = 1;
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
20
31
|
try {
|
|
21
32
|
const { data } = await api.get('/api/v1/me', true);
|
|
22
33
|
const handle = data.username || data.display_name || data.email || creds.email || 'unknown';
|
package/dist/lib/prompt.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { stdin, stdout } from 'node:process';
|
|
2
|
-
import readline from 'node:readline';
|
|
3
2
|
import chalk from 'chalk';
|
|
4
3
|
export async function checkboxPrompt(question, items, opts = {}) {
|
|
5
4
|
if (!stdin.isTTY || !stdout.isTTY) {
|
|
@@ -8,15 +7,17 @@ export async function checkboxPrompt(question, items, opts = {}) {
|
|
|
8
7
|
const selected = new Set(opts.initial ?? []);
|
|
9
8
|
let cursor = 0;
|
|
10
9
|
const minSelected = opts.minSelected ?? 0;
|
|
10
|
+
let dataHandler = null;
|
|
11
11
|
const cleanup = () => {
|
|
12
12
|
if (typeof stdin.setRawMode === 'function')
|
|
13
13
|
stdin.setRawMode(false);
|
|
14
|
-
|
|
14
|
+
if (dataHandler)
|
|
15
|
+
stdin.off('data', dataHandler);
|
|
15
16
|
stdin.pause();
|
|
16
17
|
stdout.write(showCursor);
|
|
18
|
+
stdout.write('\n');
|
|
17
19
|
};
|
|
18
20
|
return new Promise((resolve) => {
|
|
19
|
-
readline.emitKeypressEvents(stdin);
|
|
20
21
|
if (typeof stdin.setRawMode === 'function')
|
|
21
22
|
stdin.setRawMode(true);
|
|
22
23
|
stdin.resume();
|
|
@@ -45,53 +46,109 @@ export async function checkboxPrompt(question, items, opts = {}) {
|
|
|
45
46
|
? `${selected.size} selected · enter to confirm`
|
|
46
47
|
: `at least ${minSelected} required · ${selected.size} selected`)}\n`);
|
|
47
48
|
};
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Raw-byte parser. We listen to `data` directly instead of relying on
|
|
51
|
+
* `readline.emitKeypressEvents`, which is unreliable on macOS Terminal +
|
|
52
|
+
* iTerm under raw mode (the keypress event simply never fires for some
|
|
53
|
+
* users, leaving them stuck on whatever items the prompt prefilled).
|
|
54
|
+
*
|
|
55
|
+
* Sequences we care about:
|
|
56
|
+
* ESC [ A/B/C/D → arrow keys
|
|
57
|
+
* 0x0D / 0x0A → return
|
|
58
|
+
* 0x20 → space
|
|
59
|
+
* 0x03 → ctrl-c
|
|
60
|
+
* 0x1B (alone) → escape (heuristic: solo 0x1B with no follow-up)
|
|
61
|
+
* "k" / "j" / "a" → vim-style move + toggle-all
|
|
62
|
+
* "q" → cancel
|
|
63
|
+
*/
|
|
64
|
+
const handle = (data) => {
|
|
65
|
+
// An entire keystroke can arrive as one chunk on most terminals, but
|
|
66
|
+
// we guard against multi-byte chunks by walking the buffer.
|
|
67
|
+
let i = 0;
|
|
68
|
+
while (i < data.length) {
|
|
69
|
+
const b = data[i];
|
|
70
|
+
// CSI escape sequences: ESC [ X
|
|
71
|
+
if (b === 0x1b && data[i + 1] === 0x5b && data.length > i + 2) {
|
|
72
|
+
const arrow = data[i + 2];
|
|
73
|
+
if (arrow === 0x41)
|
|
74
|
+
cursor = (cursor - 1 + items.length) % items.length;
|
|
75
|
+
else if (arrow === 0x42)
|
|
76
|
+
cursor = (cursor + 1) % items.length;
|
|
77
|
+
else if (arrow === 0x43) {
|
|
78
|
+
// right arrow → toggle (some users default to this)
|
|
79
|
+
const v = items[cursor].value;
|
|
80
|
+
if (selected.has(v))
|
|
81
|
+
selected.delete(v);
|
|
82
|
+
else
|
|
83
|
+
selected.add(v);
|
|
84
|
+
}
|
|
85
|
+
i += 3;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
// Bare ESC = cancel.
|
|
89
|
+
if (b === 0x1b && (data.length === i + 1 || data[i + 1] === undefined)) {
|
|
90
|
+
cleanup();
|
|
91
|
+
resolve(null);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
// Ctrl-C.
|
|
95
|
+
if (b === 0x03) {
|
|
96
|
+
cleanup();
|
|
97
|
+
resolve(null);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
// Enter — CR or LF.
|
|
101
|
+
if (b === 0x0d || b === 0x0a) {
|
|
102
|
+
if (selected.size < minSelected) {
|
|
103
|
+
i += 1;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
cleanup();
|
|
107
|
+
resolve([...selected]);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
// Space — toggle current.
|
|
111
|
+
if (b === 0x20) {
|
|
66
112
|
const v = items[cursor].value;
|
|
67
113
|
if (selected.has(v))
|
|
68
114
|
selected.delete(v);
|
|
69
115
|
else
|
|
70
116
|
selected.add(v);
|
|
71
|
-
|
|
117
|
+
i += 1;
|
|
118
|
+
continue;
|
|
72
119
|
}
|
|
73
|
-
|
|
120
|
+
// Letters: a (toggle all), j (down), k (up), q (cancel).
|
|
121
|
+
if (b === 0x61) {
|
|
74
122
|
if (selected.size === items.length)
|
|
75
123
|
selected.clear();
|
|
76
124
|
else
|
|
77
|
-
for (const
|
|
78
|
-
selected.add(
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
125
|
+
for (const it of items)
|
|
126
|
+
selected.add(it.value);
|
|
127
|
+
i += 1;
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (b === 0x6a) {
|
|
131
|
+
cursor = (cursor + 1) % items.length;
|
|
132
|
+
i += 1;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (b === 0x6b) {
|
|
136
|
+
cursor = (cursor - 1 + items.length) % items.length;
|
|
137
|
+
i += 1;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (b === 0x71) {
|
|
88
141
|
cleanup();
|
|
89
142
|
resolve(null);
|
|
90
143
|
return;
|
|
144
|
+
}
|
|
145
|
+
// Unknown byte — advance and ignore.
|
|
146
|
+
i += 1;
|
|
91
147
|
}
|
|
92
148
|
render();
|
|
93
149
|
};
|
|
94
|
-
|
|
150
|
+
dataHandler = handle;
|
|
151
|
+
stdin.on('data', handle);
|
|
95
152
|
render();
|
|
96
153
|
});
|
|
97
154
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prave/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Prave CLI — import, export, install, sync Claude Skills.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"open": "^10.1.0",
|
|
17
17
|
"ora": "^8.0.1",
|
|
18
18
|
"undici": "^6.18.0",
|
|
19
|
-
"@prave/shared": "1.0.
|
|
19
|
+
"@prave/shared": "1.0.1"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@types/node": "^20.12.7",
|