@jacebenson/jsn 4.3.10 → 4.3.11-dev.69
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/bin/jsn.js +7 -1
- package/package.json +3 -3
- package/src/app.js +10 -23
- package/src/auth/browser-session.js +21 -0
- package/src/auth/credential-state.js +124 -0
- package/src/auth/credential-store.js +177 -0
- package/src/auth/oauth.js +66 -0
- package/src/auth.js +11 -378
- package/src/cli.js +3 -1
- package/src/commands/docs/db.js +22 -2
- package/src/commands/docs/docs.js +13 -13
- package/src/commands/docs/embed.js +33 -33
- package/src/commands/docs/ingest.js +58 -58
- package/src/commands/docs/refresh.js +91 -91
- package/src/commands/docs/search.js +108 -108
- package/src/commands/docs/serve.js +4 -1
- package/src/commands/midservers.js +1 -1
- package/src/commands/setup.js +27 -25
- package/src/commands/skill.js +1 -1
- package/src/commands/skipped-updates.js +1 -1
- package/src/commands/updatesets.js +1 -1
- package/src/commands/version.js +16 -2
- package/src/flow-designer-interactive.js +2 -2
- package/src/frontmatter.js +5 -3
- package/src/helpers.js +1 -1
- package/src/paginated-search.js +162 -161
- package/src/prompts.js +19 -7
- package/src/sdk.js +79 -92
package/src/commands/setup.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { createPrompt, useKeypress, usePagination, useState, isDownKey, isEnterKey, isUpKey } from '@inquirer/core';
|
|
2
1
|
import { loginWizard, loginProfile, showAuthStatus, switchProfile, modifyProfile, logoutProfile, removeConnection } from './auth.js';
|
|
3
2
|
import { errUsage } from '../errors.js';
|
|
4
3
|
import { declareCapabilities } from '../capabilities.js';
|
|
@@ -18,32 +17,35 @@ const HUB_CHOICES = [
|
|
|
18
17
|
];
|
|
19
18
|
|
|
20
19
|
/** The setup hub prompt; q and Escape resolve with no selected action. */
|
|
21
|
-
export const authHubPrompt =
|
|
22
|
-
const
|
|
23
|
-
|
|
20
|
+
export const authHubPrompt = async (...args) => {
|
|
21
|
+
const { createPrompt, useKeypress, usePagination, useState, isDownKey, isEnterKey, isUpKey } = await import('@inquirer/core');
|
|
22
|
+
return createPrompt((config, done) => {
|
|
23
|
+
const [active, setActive] = useState(0);
|
|
24
|
+
const choices = config.choices;
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
26
|
+
useKeypress((key) => {
|
|
27
|
+
if (key.name === 'q' || key.name === 'escape') {
|
|
28
|
+
done(undefined);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (isEnterKey(key)) {
|
|
32
|
+
done(choices[active].value);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (isUpKey(key) && active > 0) setActive(active - 1);
|
|
36
|
+
if (isDownKey(key) && active < choices.length - 1) setActive(active + 1);
|
|
37
|
+
});
|
|
37
38
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
});
|
|
39
|
+
const page = usePagination({
|
|
40
|
+
items: choices,
|
|
41
|
+
active,
|
|
42
|
+
pageSize: choices.length,
|
|
43
|
+
loop: false,
|
|
44
|
+
renderItem: ({ item, isActive }) => `${isActive ? '❯' : ' '} ${item.name}`,
|
|
45
|
+
});
|
|
46
|
+
return `${config.message}\n${page}\n\n↑↓ navigate • ⏎ select • q/Esc exit`;
|
|
47
|
+
})(...args);
|
|
48
|
+
};
|
|
47
49
|
|
|
48
50
|
/** Interactive hub: what do you want to do? Returns an action or undefined on exit. */
|
|
49
51
|
export async function authHubMenu({ promptFn = authHubPrompt, input, output } = {}) {
|
package/src/commands/skill.js
CHANGED
|
@@ -333,7 +333,7 @@ export function skillCmd(wrap) {
|
|
|
333
333
|
? `Where should the jsn skill be installed? (${outdated.length} outdated)`
|
|
334
334
|
: 'Where should the jsn skill be installed?';
|
|
335
335
|
|
|
336
|
-
const { checkbox } = await import('
|
|
336
|
+
const { checkbox } = await import('../prompts.js');
|
|
337
337
|
const choices = Object.entries(TARGET_NAMES).map(([key, label]) => {
|
|
338
338
|
const dir = AGENT_SKILL_DIRS[key];
|
|
339
339
|
const targetPath = key === 'cursor'
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { checkbox, select, confirm } from '
|
|
1
|
+
import { checkbox, select, confirm } from '../prompts.js';
|
|
2
2
|
import { canUsePicker, interactiveList, getStringField } from '../helpers.js';
|
|
3
3
|
import { declareCapabilities } from '../capabilities.js';
|
|
4
4
|
import { errUsage } from '../errors.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
-
import { editor, confirm } from '
|
|
2
|
+
import { editor, confirm } from '../prompts.js';
|
|
3
3
|
import { formatRecordForDisplay, getStringField, interactiveList, assertSafeExactMatch, canPrompt, buildRerunCommand, verifyWriteBack } from '../helpers.js';
|
|
4
4
|
import { getActiveProfile } from '../config.js';
|
|
5
5
|
import { FormatAuto } from '../output.js';
|
package/src/commands/version.js
CHANGED
|
@@ -19,14 +19,28 @@ export function getVersion() {
|
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
const backgroundChecks = new Set();
|
|
23
|
+
|
|
24
|
+
export function cancelBackgroundChecks() {
|
|
25
|
+
for (const controller of backgroundChecks) controller.abort();
|
|
26
|
+
backgroundChecks.clear();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function checkLatest({ timeout = 1500, background = false } = {}) {
|
|
30
|
+
const controller = new AbortController();
|
|
31
|
+
const timer = setTimeout(() => controller.abort(), timeout);
|
|
32
|
+
timer.unref();
|
|
33
|
+
if (background) backgroundChecks.add(controller);
|
|
23
34
|
try {
|
|
24
|
-
const res = await fetch(NPM_REGISTRY);
|
|
35
|
+
const res = await fetch(NPM_REGISTRY, { signal: controller.signal });
|
|
25
36
|
if (!res.ok) return null;
|
|
26
37
|
const data = await res.json();
|
|
27
38
|
return data.version;
|
|
28
39
|
} catch {
|
|
29
40
|
return null;
|
|
41
|
+
} finally {
|
|
42
|
+
clearTimeout(timer);
|
|
43
|
+
backgroundChecks.delete(controller);
|
|
30
44
|
}
|
|
31
45
|
}
|
|
32
46
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Terminal orchestration only. Persistence and lifecycle remain in the SDK.
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import { randomUUID, randomBytes } from 'node:crypto';
|
|
4
|
-
import { input, select, confirm, editor } from '
|
|
4
|
+
import { input, select, confirm, editor } from './prompts.js';
|
|
5
5
|
import { canPrompt, interactiveList, getStringField } from './helpers.js';
|
|
6
6
|
import { FormatAuto } from './output.js';
|
|
7
7
|
import { errUsage, errConfirmationRequired } from './errors.js';
|
|
@@ -35,7 +35,7 @@ export function interactiveDesigner(argv, app) {
|
|
|
35
35
|
export async function cancelled(operation) {
|
|
36
36
|
try { return await operation(); }
|
|
37
37
|
catch (error) {
|
|
38
|
-
if (error === CANCEL || ['ExitPromptError', 'AbortPromptError'].includes(error.name)) return undefined;
|
|
38
|
+
if (error === CANCEL || ['ExitPromptError', 'AbortPromptError', 'PromptCancelled'].includes(error.name)) return undefined;
|
|
39
39
|
// Keep direct handler errors throwable; opt into the shared JSON renderer at the CLI boundary.
|
|
40
40
|
if (error.code === 'usage_error') error.jsonEnvelope = true;
|
|
41
41
|
throw error;
|
package/src/frontmatter.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
// js-yaml 5 ships ESM-only named exports; there is no default export.
|
|
2
|
+
import { load as loadYaml, dump as dumpYaml } from 'js-yaml';
|
|
2
3
|
|
|
3
4
|
const DEFAULT_DELIMITERS = ['---', '---'];
|
|
4
5
|
|
|
@@ -27,7 +28,8 @@ export function parseFrontmatter(input, options = {}) {
|
|
|
27
28
|
|
|
28
29
|
const closeIndex = frontmatter.indexOf(`\n${close}`);
|
|
29
30
|
const raw = closeIndex === -1 ? frontmatter : frontmatter.slice(0, closeIndex);
|
|
30
|
-
|
|
31
|
+
// js-yaml 5 throws on an empty document; an empty block means no metadata.
|
|
32
|
+
const parsed = raw.trim() === '' ? {} : loadYaml(raw);
|
|
31
33
|
const data = parsed === undefined ? {} : parsed;
|
|
32
34
|
|
|
33
35
|
if (closeIndex === -1) return { data, content: '' };
|
|
@@ -41,7 +43,7 @@ export function parseFrontmatter(input, options = {}) {
|
|
|
41
43
|
/** Serialize docs metadata and body using gray-matter-compatible YAML framing. */
|
|
42
44
|
export function stringifyFrontmatter(body, data, options = {}) {
|
|
43
45
|
const [open, close] = delimiters(options);
|
|
44
|
-
const yamlText =
|
|
46
|
+
const yamlText = dumpYaml(data, options).trim();
|
|
45
47
|
const prefix = yamlText === '{}' ? '' : `${open}\n${yamlText}\n${close}\n`;
|
|
46
48
|
const content = String(body);
|
|
47
49
|
return prefix + (content.endsWith('\n') ? content : `${content}\n`);
|
package/src/helpers.js
CHANGED
|
@@ -362,7 +362,7 @@ export function assertSafeExactMatch(value) {
|
|
|
362
362
|
export async function interactiveList({ app, table, singular, columns, limit = 50, query = '', formatLabel, labelField = 'name', message, formatValue, promptFn }) {
|
|
363
363
|
const prompt = promptFn || paginatedSearch;
|
|
364
364
|
app.requireInstance();
|
|
365
|
-
const effectiveFormat = app.output.getFormat()
|
|
365
|
+
const effectiveFormat = app.output.getFormat();
|
|
366
366
|
if (!promptFn && (effectiveFormat !== FormatAuto || !canPrompt())) {
|
|
367
367
|
return null; // not interactive — caller should fall back to text/table
|
|
368
368
|
}
|
package/src/paginated-search.js
CHANGED
|
@@ -2,89 +2,110 @@
|
|
|
2
2
|
* Paginated search prompt built with @inquirer/core hooks.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
5
|
+
|
|
6
|
+
export const paginatedSearch = async (...args) => {
|
|
7
|
+
const { createPrompt, useState, useKeypress, usePagination, useEffect, useRef, isEnterKey, isUpKey, isDownKey } = await import('@inquirer/core');
|
|
8
|
+
return createPrompt((config, done) => {
|
|
9
|
+
const { message, pageSize = 10, totalCount = 0, source } = config;
|
|
10
|
+
|
|
11
|
+
const [status, setStatus] = useState('loading');
|
|
12
|
+
const [searchTerm, setSearchTerm] = useState('');
|
|
13
|
+
const [choices, setChoices] = useState([]);
|
|
14
|
+
const [active, setActive] = useState(0);
|
|
15
|
+
const [loaded, setLoaded] = useState(0);
|
|
16
|
+
const [searchMode, setSearchMode] = useState(false);
|
|
17
|
+
|
|
18
|
+
// Refs for mutable state (no functional updaters — @inquirer/core doesn't support them)
|
|
19
|
+
const choicesRef = useRef([]);
|
|
20
|
+
const loadedRef = useRef(0);
|
|
21
|
+
const activeRef = useRef(0);
|
|
22
|
+
const totalCountRef = useRef(totalCount);
|
|
23
|
+
const searchModeRef = useRef(false);
|
|
24
|
+
|
|
25
|
+
const fullMessage = totalCount > 0
|
|
26
|
+
? `${message} (${loaded} of ${totalCount} loaded)`
|
|
27
|
+
: message;
|
|
28
|
+
|
|
29
|
+
// Load initial page
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
const controller = new AbortController();
|
|
32
|
+
setStatus('loading');
|
|
33
|
+
|
|
34
|
+
const load = async () => {
|
|
35
|
+
try {
|
|
36
|
+
const items = await source(searchTerm || undefined, 0, { signal: controller.signal });
|
|
37
|
+
if (!controller.signal.aborted) {
|
|
38
|
+
choicesRef.current = items;
|
|
39
|
+
loadedRef.current = items.length;
|
|
40
|
+
activeRef.current = 0;
|
|
41
|
+
setChoices(items);
|
|
42
|
+
setLoaded(items.length);
|
|
43
|
+
setActive(0);
|
|
44
|
+
setStatus('done');
|
|
45
|
+
}
|
|
46
|
+
} catch {
|
|
47
|
+
if (!controller.signal.aborted) {
|
|
48
|
+
setStatus('done');
|
|
49
|
+
setChoices([]);
|
|
50
|
+
setLoaded(0);
|
|
51
|
+
}
|
|
51
52
|
}
|
|
53
|
+
};
|
|
54
|
+
load();
|
|
55
|
+
return () => controller.abort();
|
|
56
|
+
}, []);
|
|
57
|
+
|
|
58
|
+
// Load more when scrolling past end
|
|
59
|
+
async function loadMore() {
|
|
60
|
+
if (loadedRef.current >= totalCountRef.current || searchModeRef.current) return;
|
|
61
|
+
setStatus('loading');
|
|
62
|
+
try {
|
|
63
|
+
const newItems = await source('', loadedRef.current, { signal: new AbortController().signal });
|
|
64
|
+
const updated = choicesRef.current.concat(newItems);
|
|
65
|
+
choicesRef.current = updated;
|
|
66
|
+
loadedRef.current = updated.length;
|
|
67
|
+
setChoices([...updated]);
|
|
68
|
+
setLoaded(updated.length);
|
|
69
|
+
} catch (err) {
|
|
70
|
+
console.error('Scroll load error:', err.message || err);
|
|
71
|
+
loadedRef.current = totalCountRef.current;
|
|
72
|
+
setLoaded(totalCountRef.current);
|
|
52
73
|
}
|
|
53
|
-
|
|
54
|
-
load();
|
|
55
|
-
return () => controller.abort();
|
|
56
|
-
}, []);
|
|
57
|
-
|
|
58
|
-
// Load more when scrolling past end
|
|
59
|
-
async function loadMore() {
|
|
60
|
-
if (loadedRef.current >= totalCountRef.current || searchModeRef.current) return;
|
|
61
|
-
setStatus('loading');
|
|
62
|
-
try {
|
|
63
|
-
const newItems = await source('', loadedRef.current, { signal: new AbortController().signal });
|
|
64
|
-
const updated = choicesRef.current.concat(newItems);
|
|
65
|
-
choicesRef.current = updated;
|
|
66
|
-
loadedRef.current = updated.length;
|
|
67
|
-
setChoices([...updated]);
|
|
68
|
-
setLoaded(updated.length);
|
|
69
|
-
} catch (err) {
|
|
70
|
-
console.error('Scroll load error:', err.message || err);
|
|
71
|
-
loadedRef.current = totalCountRef.current;
|
|
72
|
-
setLoaded(totalCountRef.current);
|
|
74
|
+
setStatus('done');
|
|
73
75
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
76
|
+
|
|
77
|
+
// Search when term changes
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (!searchTerm) {
|
|
80
|
+
setSearchMode(false);
|
|
81
|
+
searchModeRef.current = false;
|
|
82
|
+
const controller = new AbortController();
|
|
83
|
+
setStatus('loading');
|
|
84
|
+
source('', 0, { signal: controller.signal }).then(items => {
|
|
85
|
+
if (!controller.signal.aborted) {
|
|
86
|
+
choicesRef.current = items;
|
|
87
|
+
loadedRef.current = Math.min(items.length, totalCount || Infinity);
|
|
88
|
+
activeRef.current = 0;
|
|
89
|
+
setChoices(items);
|
|
90
|
+
setLoaded(items.length);
|
|
91
|
+
setActive(0);
|
|
92
|
+
setStatus('done');
|
|
93
|
+
}
|
|
94
|
+
}).catch(() => {
|
|
95
|
+
if (!controller.signal.aborted) setStatus('done');
|
|
96
|
+
});
|
|
97
|
+
return () => controller.abort();
|
|
98
|
+
}
|
|
99
|
+
|
|
82
100
|
const controller = new AbortController();
|
|
83
101
|
setStatus('loading');
|
|
84
|
-
|
|
102
|
+
setSearchMode(true);
|
|
103
|
+
searchModeRef.current = true;
|
|
104
|
+
|
|
105
|
+
source(searchTerm, undefined, { signal: controller.signal }).then(items => {
|
|
85
106
|
if (!controller.signal.aborted) {
|
|
86
107
|
choicesRef.current = items;
|
|
87
|
-
loadedRef.current =
|
|
108
|
+
loadedRef.current = items.length;
|
|
88
109
|
activeRef.current = 0;
|
|
89
110
|
setChoices(items);
|
|
90
111
|
setLoaded(items.length);
|
|
@@ -92,103 +113,83 @@ export const paginatedSearch = createPrompt((config, done) => {
|
|
|
92
113
|
setStatus('done');
|
|
93
114
|
}
|
|
94
115
|
}).catch(() => {
|
|
95
|
-
if (!controller.signal.aborted)
|
|
116
|
+
if (!controller.signal.aborted) {
|
|
117
|
+
setStatus('done');
|
|
118
|
+
setChoices([]);
|
|
119
|
+
}
|
|
96
120
|
});
|
|
97
|
-
return () => controller.abort();
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const controller = new AbortController();
|
|
101
|
-
setStatus('loading');
|
|
102
|
-
setSearchMode(true);
|
|
103
|
-
searchModeRef.current = true;
|
|
104
|
-
|
|
105
|
-
source(searchTerm, undefined, { signal: controller.signal }).then(items => {
|
|
106
|
-
if (!controller.signal.aborted) {
|
|
107
|
-
choicesRef.current = items;
|
|
108
|
-
loadedRef.current = items.length;
|
|
109
|
-
activeRef.current = 0;
|
|
110
|
-
setChoices(items);
|
|
111
|
-
setLoaded(items.length);
|
|
112
|
-
setActive(0);
|
|
113
|
-
setStatus('done');
|
|
114
|
-
}
|
|
115
|
-
}).catch(() => {
|
|
116
|
-
if (!controller.signal.aborted) {
|
|
117
|
-
setStatus('done');
|
|
118
|
-
setChoices([]);
|
|
119
|
-
}
|
|
120
|
-
});
|
|
121
121
|
|
|
122
|
-
|
|
123
|
-
|
|
122
|
+
return () => controller.abort();
|
|
123
|
+
}, [searchTerm]);
|
|
124
124
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
125
|
+
// Key handling
|
|
126
|
+
useKeypress((key, rl) => {
|
|
127
|
+
if (status === 'loading') return;
|
|
128
128
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
129
|
+
if (isEnterKey(key)) {
|
|
130
|
+
if (choicesRef.current.length > 0 && activeRef.current >= 0 && activeRef.current < choicesRef.current.length) {
|
|
131
|
+
done(choicesRef.current[activeRef.current]);
|
|
132
|
+
}
|
|
133
|
+
return;
|
|
132
134
|
}
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
135
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
136
|
+
if (key.name === 'escape') {
|
|
137
|
+
done(undefined);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
140
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
141
|
+
if (isUpKey(key)) {
|
|
142
|
+
if (activeRef.current > 0) {
|
|
143
|
+
activeRef.current--;
|
|
144
|
+
setActive(activeRef.current);
|
|
145
|
+
}
|
|
146
|
+
return;
|
|
145
147
|
}
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
148
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
activeRef.current++;
|
|
152
|
-
setActive(activeRef.current);
|
|
153
|
-
} else if (!searchModeRef.current && loadedRef.current < totalCountRef.current) {
|
|
154
|
-
loadMore().then(() => {
|
|
149
|
+
if (isDownKey(key)) {
|
|
150
|
+
if (activeRef.current < choicesRef.current.length - 1) {
|
|
155
151
|
activeRef.current++;
|
|
156
152
|
setActive(activeRef.current);
|
|
157
|
-
})
|
|
153
|
+
} else if (!searchModeRef.current && loadedRef.current < totalCountRef.current) {
|
|
154
|
+
loadMore().then(() => {
|
|
155
|
+
activeRef.current++;
|
|
156
|
+
setActive(activeRef.current);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
return;
|
|
158
160
|
}
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
161
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
});
|
|
162
|
+
// Use readline's built-in line buffer for text input
|
|
163
|
+
const newTerm = rl.line;
|
|
164
|
+
if (newTerm !== searchTerm) {
|
|
165
|
+
setSearchTerm(newTerm);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Paginated rendering
|
|
170
|
+
const page = usePagination({
|
|
171
|
+
items: Array.isArray(choices) ? choices : [],
|
|
172
|
+
active,
|
|
173
|
+
pageSize,
|
|
174
|
+
renderItem: ({ item, isActive }) => {
|
|
175
|
+
const prefix = isActive ? '\x1b[7m' : ' ';
|
|
176
|
+
const suffix = isActive ? '\x1b[0m' : '';
|
|
177
|
+
const name = item.name || String(item.value || item);
|
|
178
|
+
const display = name.length > 70 ? name.slice(0, 67) + '...' : name;
|
|
179
|
+
return ` ${prefix}${display}${suffix}`;
|
|
180
|
+
},
|
|
181
|
+
loop: false,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const helpText = loaded < totalCount && !searchMode
|
|
185
|
+
? '(scroll to load more, type to search, Esc to cancel)'
|
|
186
|
+
: '(type to search, Esc to cancel)';
|
|
187
|
+
|
|
188
|
+
return [
|
|
189
|
+
`${fullMessage} Search: ${searchTerm}${status === 'loading' ? ' ...' : ''}`,
|
|
190
|
+
`${page}\n\n${helpText}`,
|
|
191
|
+
];
|
|
192
|
+
})(...args);
|
|
193
|
+
};
|
|
193
194
|
|
|
194
195
|
export default paginatedSearch;
|
package/src/prompts.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import * as prompts from '@inquirer/prompts';
|
|
2
|
-
|
|
3
1
|
export class PromptCancelled extends Error {
|
|
4
2
|
constructor() {
|
|
5
3
|
super('Cancelled.');
|
|
@@ -30,8 +28,22 @@ export function cancellablePrompt(prompt) {
|
|
|
30
28
|
};
|
|
31
29
|
}
|
|
32
30
|
|
|
33
|
-
export const input =
|
|
34
|
-
export const password =
|
|
35
|
-
export const select =
|
|
36
|
-
export const search =
|
|
37
|
-
export const confirm =
|
|
31
|
+
export const input = lazyPrompt('input');
|
|
32
|
+
export const password = lazyPrompt('password');
|
|
33
|
+
export const select = lazyPrompt('select');
|
|
34
|
+
export const search = lazyPrompt('search');
|
|
35
|
+
export const confirm = lazyPrompt('confirm');
|
|
36
|
+
export const checkbox = lazyPrompt('checkbox');
|
|
37
|
+
|
|
38
|
+
// The editor prompt hands the keyboard to $EDITOR, so Escape belongs to that
|
|
39
|
+
// editor. It lives here so every command imports prompts from one module;
|
|
40
|
+
// cancelling an editor still uses Inquirer's Ctrl+C abort.
|
|
41
|
+
export const editor = async (...args) => (await import('@inquirer/prompts')).editor(...args);
|
|
42
|
+
|
|
43
|
+
// Load Inquirer only when a prompt is actually shown.
|
|
44
|
+
function lazyPrompt(name) {
|
|
45
|
+
return async (...args) => {
|
|
46
|
+
const prompts = await import('@inquirer/prompts');
|
|
47
|
+
return cancellablePrompt(prompts[name])(...args);
|
|
48
|
+
};
|
|
49
|
+
}
|