@rbbtsn0w/adg 0.1.0-alpha.1 → 0.1.0-beta.2
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/bin/adg.js +703 -0
- package/dist/src/adapters/anthropic.js +54 -0
- package/dist/src/adapters/index.js +10 -0
- package/dist/src/adapters/openai.js +30 -0
- package/dist/src/adapters/reverse.js +53 -0
- package/dist/src/agents/claude.js +118 -0
- package/dist/src/agents/codex.js +61 -0
- package/{src/agents/index.ts → dist/src/agents/index.js} +6 -8
- package/dist/src/agents/registry.js +24 -0
- package/dist/src/agents/types.js +1 -0
- package/dist/src/commands/adapt.js +26 -0
- package/dist/src/commands/import.js +51 -0
- package/dist/src/commands/init.js +104 -0
- package/dist/src/commands/install.js +257 -0
- package/dist/src/commands/link.js +34 -0
- package/dist/src/commands/list.js +19 -0
- package/dist/src/commands/marketplace.js +124 -0
- package/dist/src/commands/migrate.js +60 -0
- package/dist/src/commands/multiselect-skills.js +103 -0
- package/dist/src/commands/remove.js +102 -0
- package/dist/src/commands/select-agents.js +40 -0
- package/dist/src/commands/select-components.js +61 -0
- package/dist/src/commands/select-plugins.js +25 -0
- package/dist/src/commands/select-scope.js +20 -0
- package/dist/src/commands/update.js +50 -0
- package/dist/src/commands/validate.js +50 -0
- package/dist/src/components.js +90 -0
- package/dist/src/deps.js +46 -0
- package/dist/src/fsutil.js +32 -0
- package/dist/src/hash.js +51 -0
- package/dist/src/lock.js +51 -0
- package/dist/src/manifest.js +110 -0
- package/dist/src/marketplace.js +39 -0
- package/{src/package.ts → dist/src/package.js} +37 -42
- package/{src/paths.ts → dist/src/paths.js} +54 -60
- package/dist/src/semver.js +55 -0
- package/dist/src/skills.js +79 -0
- package/dist/src/sources.js +122 -0
- package/dist/src/types.js +19 -0
- package/dist/vendor/skills/package.json +143 -0
- package/dist/vendor/skills/src/add.js +1663 -0
- package/dist/vendor/skills/src/agents.js +729 -0
- package/dist/vendor/skills/src/blob.js +436 -0
- package/dist/vendor/skills/src/cli.js +340 -0
- package/dist/vendor/skills/src/constants.js +3 -0
- package/dist/vendor/skills/src/detect-agent.js +56 -0
- package/dist/vendor/skills/src/find.js +294 -0
- package/dist/vendor/skills/src/frontmatter.js +13 -0
- package/dist/vendor/skills/src/git-tree.js +32 -0
- package/dist/vendor/skills/src/git.js +235 -0
- package/dist/vendor/skills/src/install.js +75 -0
- package/dist/vendor/skills/src/installer.js +924 -0
- package/dist/vendor/skills/src/list.js +201 -0
- package/dist/vendor/skills/src/local-lock.js +109 -0
- package/dist/vendor/skills/src/plugin-manifest.js +152 -0
- package/dist/vendor/skills/src/prompts/search-multiselect.js +312 -0
- package/dist/vendor/skills/src/providers/index.js +4 -0
- package/dist/vendor/skills/src/providers/registry.js +42 -0
- package/dist/vendor/skills/src/providers/types.js +1 -0
- package/dist/vendor/skills/src/providers/wellknown.js +625 -0
- package/dist/vendor/skills/src/remove.js +263 -0
- package/dist/vendor/skills/src/sanitize.js +57 -0
- package/dist/vendor/skills/src/self-cli.js +15 -0
- package/dist/vendor/skills/src/skill-lock.js +237 -0
- package/dist/vendor/skills/src/skills.js +264 -0
- package/dist/vendor/skills/src/source-parser.js +367 -0
- package/dist/vendor/skills/src/sync.js +404 -0
- package/dist/vendor/skills/src/telemetry.js +101 -0
- package/dist/vendor/skills/src/test-utils.js +59 -0
- package/dist/vendor/skills/src/types.js +1 -0
- package/dist/vendor/skills/src/update-source.js +76 -0
- package/dist/vendor/skills/src/update.js +590 -0
- package/dist/vendor/skills/src/use.js +505 -0
- package/package.json +15 -7
- package/bin/adg.ts +0 -758
- package/src/adapters/anthropic.ts +0 -54
- package/src/adapters/index.ts +0 -24
- package/src/adapters/openai.ts +0 -37
- package/src/adapters/reverse.ts +0 -60
- package/src/agents/claude.ts +0 -124
- package/src/agents/codex.ts +0 -67
- package/src/agents/registry.ts +0 -30
- package/src/agents/types.ts +0 -47
- package/src/commands/adapt.ts +0 -36
- package/src/commands/import.ts +0 -69
- package/src/commands/init.ts +0 -146
- package/src/commands/install.ts +0 -411
- package/src/commands/link.ts +0 -61
- package/src/commands/list.ts +0 -28
- package/src/commands/marketplace.ts +0 -198
- package/src/commands/migrate.ts +0 -84
- package/src/commands/multiselect-skills.ts +0 -137
- package/src/commands/remove.ts +0 -136
- package/src/commands/select-agents.ts +0 -45
- package/src/commands/select-components.ts +0 -66
- package/src/commands/select-plugins.ts +0 -28
- package/src/commands/select-scope.ts +0 -21
- package/src/commands/update.ts +0 -85
- package/src/commands/validate.ts +0 -57
- package/src/components.ts +0 -90
- package/src/deps.ts +0 -64
- package/src/fsutil.ts +0 -38
- package/src/hash.ts +0 -61
- package/src/lock.ts +0 -57
- package/src/manifest.ts +0 -113
- package/src/marketplace.ts +0 -41
- package/src/semver.ts +0 -67
- package/src/skills.ts +0 -88
- package/src/sources.ts +0 -159
- package/src/types.ts +0 -140
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import * as readline from 'readline';
|
|
2
|
+
import { stripVTControlCharacters } from 'node:util';
|
|
3
|
+
import { Writable } from 'stream';
|
|
4
|
+
import pc from 'picocolors';
|
|
5
|
+
// Silent writable stream to prevent readline from echoing input
|
|
6
|
+
const silentOutput = new Writable({
|
|
7
|
+
write(_chunk, _encoding, callback) {
|
|
8
|
+
callback();
|
|
9
|
+
},
|
|
10
|
+
});
|
|
11
|
+
const S_STEP_ACTIVE = pc.green('◆');
|
|
12
|
+
const S_STEP_CANCEL = pc.red('■');
|
|
13
|
+
const S_STEP_SUBMIT = pc.green('◇');
|
|
14
|
+
const S_RADIO_ACTIVE = pc.green('●');
|
|
15
|
+
const S_RADIO_INACTIVE = pc.dim('○');
|
|
16
|
+
const S_CHECKBOX_LOCKED = pc.green('✓');
|
|
17
|
+
const S_BULLET = pc.green('•');
|
|
18
|
+
const S_BAR = pc.dim('│');
|
|
19
|
+
const S_BAR_H = pc.dim('─');
|
|
20
|
+
export const cancelSymbol = Symbol('cancel');
|
|
21
|
+
/**
|
|
22
|
+
* Approximate terminal display width (cells) for a string with no ANSI sequences.
|
|
23
|
+
* Matches common East Asian / emoji double-width behavior used by modern terminals.
|
|
24
|
+
*/
|
|
25
|
+
export function approxStringWidth(plain) {
|
|
26
|
+
let width = 0;
|
|
27
|
+
for (const ch of plain) {
|
|
28
|
+
const code = ch.codePointAt(0);
|
|
29
|
+
if (code === 0)
|
|
30
|
+
continue;
|
|
31
|
+
const wide = (code >= 0x1100 && code <= 0x115f) ||
|
|
32
|
+
(code >= 0x231a && code <= 0x231b) ||
|
|
33
|
+
(code >= 0x2329 && code <= 0x232a) ||
|
|
34
|
+
(code >= 0x23e9 && code <= 0x23ec) ||
|
|
35
|
+
code === 0x23f0 ||
|
|
36
|
+
code === 0x23f3 ||
|
|
37
|
+
(code >= 0x25fd && code <= 0x25fe) ||
|
|
38
|
+
(code >= 0x2614 && code <= 0x2615) ||
|
|
39
|
+
(code >= 0x2648 && code <= 0x2653) ||
|
|
40
|
+
(code >= 0x267f && code <= 0x267f) ||
|
|
41
|
+
(code >= 0x2693 && code <= 0x2693) ||
|
|
42
|
+
(code >= 0x26a1 && code <= 0x26a1) ||
|
|
43
|
+
(code >= 0x26aa && code <= 0x26ab) ||
|
|
44
|
+
(code >= 0x26bd && code <= 0x26be) ||
|
|
45
|
+
(code >= 0x26c4 && code <= 0x26c5) ||
|
|
46
|
+
(code >= 0x26ce && code <= 0x26ce) ||
|
|
47
|
+
(code >= 0x26d4 && code <= 0x26d4) ||
|
|
48
|
+
(code >= 0x26ea && code <= 0x26ea) ||
|
|
49
|
+
(code >= 0x26f2 && code <= 0x26f3) ||
|
|
50
|
+
(code >= 0x26f5 && code <= 0x26f5) ||
|
|
51
|
+
(code >= 0x26fa && code <= 0x26fa) ||
|
|
52
|
+
(code >= 0x26fd && code <= 0x26fd) ||
|
|
53
|
+
(code >= 0x2705 && code <= 0x2705) ||
|
|
54
|
+
(code >= 0x270a && code <= 0x270b) ||
|
|
55
|
+
(code >= 0x2728 && code <= 0x2728) ||
|
|
56
|
+
(code >= 0x274c && code <= 0x274c) ||
|
|
57
|
+
(code >= 0x274e && code <= 0x274e) ||
|
|
58
|
+
(code >= 0x2753 && code <= 0x2755) ||
|
|
59
|
+
(code >= 0x2757 && code <= 0x2757) ||
|
|
60
|
+
(code >= 0x2795 && code <= 0x2797) ||
|
|
61
|
+
(code >= 0x27b0 && code <= 0x27b0) ||
|
|
62
|
+
(code >= 0x27bf && code <= 0x27bf) ||
|
|
63
|
+
(code >= 0x2b1b && code <= 0x2b1c) ||
|
|
64
|
+
(code >= 0x2b50 && code <= 0x2b50) ||
|
|
65
|
+
(code >= 0x2b55 && code <= 0x2b55) ||
|
|
66
|
+
(code >= 0x2e80 && code <= 0xa4cf && code !== 0x303f) ||
|
|
67
|
+
(code >= 0xa960 && code <= 0xa97c) ||
|
|
68
|
+
(code >= 0xac00 && code <= 0xd7a3) ||
|
|
69
|
+
(code >= 0xf900 && code <= 0xfaff) ||
|
|
70
|
+
(code >= 0xfe10 && code <= 0xfe19) ||
|
|
71
|
+
(code >= 0xfe30 && code <= 0xfe6f) ||
|
|
72
|
+
(code >= 0xff00 && code <= 0xff60) ||
|
|
73
|
+
(code >= 0xffe0 && code <= 0xffe6) ||
|
|
74
|
+
(code >= 0x1f000 && code <= 0x1f9ff);
|
|
75
|
+
width += wide ? 2 : 1;
|
|
76
|
+
}
|
|
77
|
+
return width;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* How many physical terminal rows one logical line occupies after soft-wrapping.
|
|
81
|
+
*/
|
|
82
|
+
export function visualRowsForLine(line, columns) {
|
|
83
|
+
const plain = stripVTControlCharacters(line);
|
|
84
|
+
const cols = Math.max(1, columns);
|
|
85
|
+
const w = approxStringWidth(plain);
|
|
86
|
+
return Math.max(1, Math.ceil(w / cols));
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Total physical rows for a block of logical lines (used to erase/redraw TUI output).
|
|
90
|
+
*/
|
|
91
|
+
export function countVisualRowsForLines(lines, columns) {
|
|
92
|
+
const cols = columns !== undefined && columns > 0
|
|
93
|
+
? columns
|
|
94
|
+
: process.stdout.columns && process.stdout.columns > 0
|
|
95
|
+
? process.stdout.columns
|
|
96
|
+
: 80;
|
|
97
|
+
return lines.reduce((sum, line) => sum + visualRowsForLine(line, cols), 0);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Interactive search multiselect prompt.
|
|
101
|
+
* Allows users to filter a long list by typing and select multiple items.
|
|
102
|
+
* Optionally supports a "locked" section that displays always-selected items.
|
|
103
|
+
*/
|
|
104
|
+
export async function searchMultiselect(options) {
|
|
105
|
+
const { message, items, maxVisible = 8, initialSelected = [], required = false, lockedSection, } = options;
|
|
106
|
+
return new Promise((resolve) => {
|
|
107
|
+
const rl = readline.createInterface({
|
|
108
|
+
input: process.stdin,
|
|
109
|
+
output: silentOutput,
|
|
110
|
+
terminal: false,
|
|
111
|
+
});
|
|
112
|
+
// Enable raw mode for keypress detection
|
|
113
|
+
if (process.stdin.isTTY) {
|
|
114
|
+
process.stdin.setRawMode(true);
|
|
115
|
+
}
|
|
116
|
+
readline.emitKeypressEvents(process.stdin, rl);
|
|
117
|
+
let query = '';
|
|
118
|
+
let cursor = 0;
|
|
119
|
+
const selected = new Set(initialSelected);
|
|
120
|
+
let lastRenderHeight = 0;
|
|
121
|
+
// Locked items are always included in the result
|
|
122
|
+
const lockedValues = lockedSection ? lockedSection.items.map((i) => i.value) : [];
|
|
123
|
+
const filter = (item, q) => {
|
|
124
|
+
if (!q)
|
|
125
|
+
return true;
|
|
126
|
+
const lowerQ = q.toLowerCase();
|
|
127
|
+
return (item.label.toLowerCase().includes(lowerQ) ||
|
|
128
|
+
String(item.value).toLowerCase().includes(lowerQ));
|
|
129
|
+
};
|
|
130
|
+
const getFiltered = () => {
|
|
131
|
+
return items.filter((item) => filter(item, query));
|
|
132
|
+
};
|
|
133
|
+
const clearRender = () => {
|
|
134
|
+
if (lastRenderHeight > 0) {
|
|
135
|
+
// Move up and clear each line
|
|
136
|
+
process.stdout.write(`\x1b[${lastRenderHeight}A`);
|
|
137
|
+
for (let i = 0; i < lastRenderHeight; i++) {
|
|
138
|
+
process.stdout.write('\x1b[2K\x1b[1B');
|
|
139
|
+
}
|
|
140
|
+
process.stdout.write(`\x1b[${lastRenderHeight}A`);
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
const render = (state = 'active') => {
|
|
144
|
+
clearRender();
|
|
145
|
+
const lines = [];
|
|
146
|
+
const filtered = getFiltered();
|
|
147
|
+
// Header
|
|
148
|
+
const icon = state === 'active' ? S_STEP_ACTIVE : state === 'cancel' ? S_STEP_CANCEL : S_STEP_SUBMIT;
|
|
149
|
+
lines.push(`${icon} ${pc.bold(message)}`);
|
|
150
|
+
if (state === 'active') {
|
|
151
|
+
// Locked section (universal agents)
|
|
152
|
+
if (lockedSection && lockedSection.items.length > 0) {
|
|
153
|
+
lines.push(`${S_BAR}`);
|
|
154
|
+
const lockedTitle = `${pc.bold(lockedSection.title)} ${pc.dim('── always included')}`;
|
|
155
|
+
lines.push(`${S_BAR} ${S_BAR_H}${S_BAR_H} ${lockedTitle} ${S_BAR_H.repeat(12)}`);
|
|
156
|
+
for (const item of lockedSection.items) {
|
|
157
|
+
lines.push(`${S_BAR} ${S_BULLET} ${pc.bold(item.label)}`);
|
|
158
|
+
}
|
|
159
|
+
if (lockedSection.hiddenCount && lockedSection.hiddenCount > 0) {
|
|
160
|
+
lines.push(`${S_BAR} ${pc.dim(`...and ${lockedSection.hiddenCount} more`)}`);
|
|
161
|
+
}
|
|
162
|
+
lines.push(`${S_BAR}`);
|
|
163
|
+
lines.push(`${S_BAR} ${S_BAR_H}${S_BAR_H} ${pc.bold('Additional agents')} ${S_BAR_H.repeat(29)}`);
|
|
164
|
+
}
|
|
165
|
+
// Search input
|
|
166
|
+
const searchLine = `${S_BAR} ${pc.dim('Search:')} ${query}${pc.inverse(' ')}`;
|
|
167
|
+
lines.push(searchLine);
|
|
168
|
+
// Hint
|
|
169
|
+
lines.push(`${S_BAR} ${pc.dim('↑↓ move, space select, enter confirm')}`);
|
|
170
|
+
lines.push(`${S_BAR}`);
|
|
171
|
+
// Items
|
|
172
|
+
const visibleStart = Math.max(0, Math.min(cursor - Math.floor(maxVisible / 2), filtered.length - maxVisible));
|
|
173
|
+
const visibleEnd = Math.min(filtered.length, visibleStart + maxVisible);
|
|
174
|
+
const visibleItems = filtered.slice(visibleStart, visibleEnd);
|
|
175
|
+
if (filtered.length === 0) {
|
|
176
|
+
lines.push(`${S_BAR} ${pc.dim('No matches found')}`);
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
for (let i = 0; i < visibleItems.length; i++) {
|
|
180
|
+
const item = visibleItems[i];
|
|
181
|
+
const actualIndex = visibleStart + i;
|
|
182
|
+
const isSelected = selected.has(item.value);
|
|
183
|
+
const isCursor = actualIndex === cursor;
|
|
184
|
+
const radio = isSelected ? S_RADIO_ACTIVE : S_RADIO_INACTIVE;
|
|
185
|
+
const label = isCursor ? pc.underline(item.label) : item.label;
|
|
186
|
+
const hint = item.hint ? pc.dim(` (${item.hint})`) : '';
|
|
187
|
+
const prefix = isCursor ? pc.cyan('❯') : ' ';
|
|
188
|
+
lines.push(`${S_BAR} ${prefix} ${radio} ${label}${hint}`);
|
|
189
|
+
}
|
|
190
|
+
// Show count if more items
|
|
191
|
+
const hiddenBefore = visibleStart;
|
|
192
|
+
const hiddenAfter = filtered.length - visibleEnd;
|
|
193
|
+
if (hiddenBefore > 0 || hiddenAfter > 0) {
|
|
194
|
+
const parts = [];
|
|
195
|
+
if (hiddenBefore > 0)
|
|
196
|
+
parts.push(`↑ ${hiddenBefore} more`);
|
|
197
|
+
if (hiddenAfter > 0)
|
|
198
|
+
parts.push(`↓ ${hiddenAfter} more`);
|
|
199
|
+
lines.push(`${S_BAR} ${pc.dim(parts.join(' '))}`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
// Selected summary (include locked items)
|
|
203
|
+
lines.push(`${S_BAR}`);
|
|
204
|
+
const allSelectedLabels = [
|
|
205
|
+
...(lockedSection ? lockedSection.items.map((i) => i.label) : []),
|
|
206
|
+
...items.filter((item) => selected.has(item.value)).map((item) => item.label),
|
|
207
|
+
];
|
|
208
|
+
if (allSelectedLabels.length === 0) {
|
|
209
|
+
lines.push(`${S_BAR} ${pc.dim('Selected: (none)')}`);
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
const summary = allSelectedLabels.length <= 3
|
|
213
|
+
? allSelectedLabels.join(', ')
|
|
214
|
+
: `${allSelectedLabels.slice(0, 3).join(', ')} +${allSelectedLabels.length - 3} more`;
|
|
215
|
+
lines.push(`${S_BAR} ${pc.green('Selected:')} ${summary}`);
|
|
216
|
+
}
|
|
217
|
+
lines.push(`${pc.dim('└')}`);
|
|
218
|
+
}
|
|
219
|
+
else if (state === 'submit') {
|
|
220
|
+
// Final state - show what was selected (including locked)
|
|
221
|
+
const allSelectedLabels = [
|
|
222
|
+
...(lockedSection ? lockedSection.items.map((i) => i.label) : []),
|
|
223
|
+
...items.filter((item) => selected.has(item.value)).map((item) => item.label),
|
|
224
|
+
];
|
|
225
|
+
lines.push(`${S_BAR} ${pc.dim(allSelectedLabels.join(', '))}`);
|
|
226
|
+
}
|
|
227
|
+
else if (state === 'cancel') {
|
|
228
|
+
lines.push(`${S_BAR} ${pc.strikethrough(pc.dim('Cancelled'))}`);
|
|
229
|
+
}
|
|
230
|
+
process.stdout.write(lines.join('\n') + '\n');
|
|
231
|
+
// Use wrapped row count: logical lines can span multiple terminal rows when hints
|
|
232
|
+
// or labels exceed column width. Using lines.length alone under-counts and breaks
|
|
233
|
+
// clearRender(), causing the prompt to re-print hundreds of times on each redraw.
|
|
234
|
+
lastRenderHeight = countVisualRowsForLines(lines, process.stdout.columns);
|
|
235
|
+
};
|
|
236
|
+
const cleanup = () => {
|
|
237
|
+
process.stdin.removeListener('keypress', keypressHandler);
|
|
238
|
+
if (process.stdin.isTTY) {
|
|
239
|
+
process.stdin.setRawMode(false);
|
|
240
|
+
}
|
|
241
|
+
rl.close();
|
|
242
|
+
};
|
|
243
|
+
const submit = () => {
|
|
244
|
+
// If required and no locked items, don't allow submitting with no selection
|
|
245
|
+
if (required && selected.size === 0 && lockedValues.length === 0) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
render('submit');
|
|
249
|
+
cleanup();
|
|
250
|
+
// Include locked values in the result
|
|
251
|
+
resolve([...lockedValues, ...Array.from(selected)]);
|
|
252
|
+
};
|
|
253
|
+
const cancel = () => {
|
|
254
|
+
render('cancel');
|
|
255
|
+
cleanup();
|
|
256
|
+
resolve(cancelSymbol);
|
|
257
|
+
};
|
|
258
|
+
// Handle keypresses
|
|
259
|
+
const keypressHandler = (_str, key) => {
|
|
260
|
+
if (!key)
|
|
261
|
+
return;
|
|
262
|
+
const filtered = getFiltered();
|
|
263
|
+
if (key.name === 'return') {
|
|
264
|
+
submit();
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (key.name === 'escape' || (key.ctrl && key.name === 'c')) {
|
|
268
|
+
cancel();
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
if (key.name === 'up') {
|
|
272
|
+
cursor = Math.max(0, cursor - 1);
|
|
273
|
+
render();
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (key.name === 'down') {
|
|
277
|
+
cursor = Math.min(filtered.length - 1, cursor + 1);
|
|
278
|
+
render();
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (key.name === 'space') {
|
|
282
|
+
const item = filtered[cursor];
|
|
283
|
+
if (item) {
|
|
284
|
+
if (selected.has(item.value)) {
|
|
285
|
+
selected.delete(item.value);
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
selected.add(item.value);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
render();
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
if (key.name === 'backspace') {
|
|
295
|
+
query = query.slice(0, -1);
|
|
296
|
+
cursor = 0;
|
|
297
|
+
render();
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
// Regular character input
|
|
301
|
+
if (key.sequence && !key.ctrl && !key.meta && key.sequence.length === 1) {
|
|
302
|
+
query += key.sequence;
|
|
303
|
+
cursor = 0;
|
|
304
|
+
render();
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
process.stdin.on('keypress', keypressHandler);
|
|
309
|
+
// Initial render
|
|
310
|
+
render();
|
|
311
|
+
});
|
|
312
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
class ProviderRegistryImpl {
|
|
2
|
+
providers = [];
|
|
3
|
+
register(provider) {
|
|
4
|
+
// Check for duplicate IDs
|
|
5
|
+
if (this.providers.some((p) => p.id === provider.id)) {
|
|
6
|
+
throw new Error(`Provider with id "${provider.id}" already registered`);
|
|
7
|
+
}
|
|
8
|
+
this.providers.push(provider);
|
|
9
|
+
}
|
|
10
|
+
findProvider(url) {
|
|
11
|
+
for (const provider of this.providers) {
|
|
12
|
+
const match = provider.match(url);
|
|
13
|
+
if (match.matches) {
|
|
14
|
+
return provider;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
getProviders() {
|
|
20
|
+
return [...this.providers];
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
// Singleton registry instance
|
|
24
|
+
export const registry = new ProviderRegistryImpl();
|
|
25
|
+
/**
|
|
26
|
+
* Register a provider with the global registry.
|
|
27
|
+
*/
|
|
28
|
+
export function registerProvider(provider) {
|
|
29
|
+
registry.register(provider);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Find a provider that matches the given URL.
|
|
33
|
+
*/
|
|
34
|
+
export function findProvider(url) {
|
|
35
|
+
return registry.findProvider(url);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Get all registered providers.
|
|
39
|
+
*/
|
|
40
|
+
export function getProviders() {
|
|
41
|
+
return registry.getProviders();
|
|
42
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|