@dealdeploy/skl 0.1.6 → 0.1.8
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/.agents/skills/opentui/SKILL.md +198 -0
- package/.agents/skills/opentui/references/animation/REFERENCE.md +431 -0
- package/.agents/skills/opentui/references/components/REFERENCE.md +143 -0
- package/.agents/skills/opentui/references/components/code-diff.md +496 -0
- package/.agents/skills/opentui/references/components/containers.md +412 -0
- package/.agents/skills/opentui/references/components/inputs.md +531 -0
- package/.agents/skills/opentui/references/components/text-display.md +384 -0
- package/.agents/skills/opentui/references/core/REFERENCE.md +145 -0
- package/.agents/skills/opentui/references/core/api.md +506 -0
- package/.agents/skills/opentui/references/core/configuration.md +166 -0
- package/.agents/skills/opentui/references/core/gotchas.md +393 -0
- package/.agents/skills/opentui/references/core/patterns.md +448 -0
- package/.agents/skills/opentui/references/keyboard/REFERENCE.md +511 -0
- package/.agents/skills/opentui/references/layout/REFERENCE.md +337 -0
- package/.agents/skills/opentui/references/layout/patterns.md +444 -0
- package/.agents/skills/opentui/references/react/REFERENCE.md +174 -0
- package/.agents/skills/opentui/references/react/api.md +435 -0
- package/.agents/skills/opentui/references/react/configuration.md +301 -0
- package/.agents/skills/opentui/references/react/gotchas.md +443 -0
- package/.agents/skills/opentui/references/react/patterns.md +501 -0
- package/.agents/skills/opentui/references/solid/REFERENCE.md +201 -0
- package/.agents/skills/opentui/references/solid/api.md +543 -0
- package/.agents/skills/opentui/references/solid/configuration.md +315 -0
- package/.agents/skills/opentui/references/solid/gotchas.md +415 -0
- package/.agents/skills/opentui/references/solid/patterns.md +558 -0
- package/.agents/skills/opentui/references/testing/REFERENCE.md +614 -0
- package/.claude/settings.local.json +11 -0
- package/.claude/skills/opentui/SKILL.md +198 -0
- package/.claude/skills/opentui/references/animation/REFERENCE.md +431 -0
- package/.claude/skills/opentui/references/components/REFERENCE.md +143 -0
- package/.claude/skills/opentui/references/components/code-diff.md +496 -0
- package/.claude/skills/opentui/references/components/containers.md +412 -0
- package/.claude/skills/opentui/references/components/inputs.md +531 -0
- package/.claude/skills/opentui/references/components/text-display.md +384 -0
- package/.claude/skills/opentui/references/core/REFERENCE.md +145 -0
- package/.claude/skills/opentui/references/core/api.md +506 -0
- package/.claude/skills/opentui/references/core/configuration.md +166 -0
- package/.claude/skills/opentui/references/core/gotchas.md +393 -0
- package/.claude/skills/opentui/references/core/patterns.md +448 -0
- package/.claude/skills/opentui/references/keyboard/REFERENCE.md +511 -0
- package/.claude/skills/opentui/references/layout/REFERENCE.md +337 -0
- package/.claude/skills/opentui/references/layout/patterns.md +444 -0
- package/.claude/skills/opentui/references/react/REFERENCE.md +174 -0
- package/.claude/skills/opentui/references/react/api.md +435 -0
- package/.claude/skills/opentui/references/react/configuration.md +301 -0
- package/.claude/skills/opentui/references/react/gotchas.md +443 -0
- package/.claude/skills/opentui/references/react/patterns.md +501 -0
- package/.claude/skills/opentui/references/solid/REFERENCE.md +201 -0
- package/.claude/skills/opentui/references/solid/api.md +543 -0
- package/.claude/skills/opentui/references/solid/configuration.md +315 -0
- package/.claude/skills/opentui/references/solid/gotchas.md +415 -0
- package/.claude/skills/opentui/references/solid/patterns.md +558 -0
- package/.claude/skills/opentui/references/testing/REFERENCE.md +614 -0
- package/index.ts +429 -86
- package/package.json +2 -1
- package/update.ts +87 -0
package/index.ts
CHANGED
|
@@ -17,11 +17,15 @@ import {
|
|
|
17
17
|
mkdirSync,
|
|
18
18
|
existsSync,
|
|
19
19
|
rmSync,
|
|
20
|
+
renameSync,
|
|
21
|
+
cpSync,
|
|
22
|
+
readSync,
|
|
20
23
|
} from "fs";
|
|
21
24
|
import { join, resolve } from "path";
|
|
22
25
|
import { homedir } from "os";
|
|
23
26
|
|
|
24
|
-
|
|
27
|
+
// @ts-ignore - bun supports JSON imports
|
|
28
|
+
const { version: VERSION } = await import("./package.json");
|
|
25
29
|
|
|
26
30
|
// ── Flags ────────────────────────────────────────────────────────────
|
|
27
31
|
const arg = process.argv[2];
|
|
@@ -35,6 +39,10 @@ if (arg === "add") {
|
|
|
35
39
|
await import("./add.ts");
|
|
36
40
|
process.exit(0);
|
|
37
41
|
}
|
|
42
|
+
if (arg === "update") {
|
|
43
|
+
await import("./update.ts");
|
|
44
|
+
process.exit(0);
|
|
45
|
+
}
|
|
38
46
|
|
|
39
47
|
try {
|
|
40
48
|
|
|
@@ -42,6 +50,7 @@ try {
|
|
|
42
50
|
|
|
43
51
|
const LIBRARY = join(homedir(), "dotfiles/skills");
|
|
44
52
|
const GLOBAL_DIR = join(homedir(), ".agents/skills");
|
|
53
|
+
const GLOBAL_CLAUDE_DIR = join(homedir(), ".claude/skills");
|
|
45
54
|
|
|
46
55
|
if (!existsSync(LIBRARY)) {
|
|
47
56
|
console.error(`skl: library not found at ${LIBRARY}`);
|
|
@@ -56,15 +65,17 @@ function ellipsize(text: string, max: number): string {
|
|
|
56
65
|
return `${text.slice(0, max - 1)}…`;
|
|
57
66
|
}
|
|
58
67
|
|
|
59
|
-
// Safety: ensure
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
68
|
+
// Safety: ensure skill dirs aren't symlinks pointing at the library itself
|
|
69
|
+
for (const [dir, label] of [[GLOBAL_DIR, "~/.agents/skills"], [GLOBAL_CLAUDE_DIR, "~/.claude/skills"]] as const) {
|
|
70
|
+
try {
|
|
71
|
+
if (lstatSync(dir).isSymbolicLink() && resolve(readlinkSync(dir)) === resolve(LIBRARY)) {
|
|
72
|
+
console.error(`ERROR: ${label} is a symlink to the library — it must be a real directory.`);
|
|
73
|
+
console.error(`Remove it and create a real directory: rm ${label} && mkdir ${label}`);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
} catch {
|
|
77
|
+
// doesn't exist yet, that's fine — toggle will mkdir
|
|
65
78
|
}
|
|
66
|
-
} catch {
|
|
67
|
-
// doesn't exist yet, that's fine — toggle will mkdir
|
|
68
79
|
}
|
|
69
80
|
|
|
70
81
|
function isLibraryLink(dir: string, name: string): boolean {
|
|
@@ -88,8 +99,35 @@ function nonLibraryExists(dir: string, name: string): boolean {
|
|
|
88
99
|
}
|
|
89
100
|
}
|
|
90
101
|
|
|
102
|
+
function isLocalDir(dir: string): boolean {
|
|
103
|
+
return dir !== GLOBAL_DIR && dir !== GLOBAL_CLAUDE_DIR;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function isEnabled(dir: string, name: string): boolean {
|
|
107
|
+
if (isLocalDir(dir)) return existsSync(join(dir, name));
|
|
108
|
+
return isLibraryLink(dir, name);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function isBlocked(dir: string, name: string): boolean {
|
|
112
|
+
if (isLocalDir(dir)) return false;
|
|
113
|
+
return nonLibraryExists(dir, name);
|
|
114
|
+
}
|
|
115
|
+
|
|
91
116
|
function toggle(dir: string, name: string): boolean {
|
|
92
117
|
const full = join(dir, name);
|
|
118
|
+
if (isLocalDir(dir)) {
|
|
119
|
+
// Copy-based toggle for project-local dirs
|
|
120
|
+
if (existsSync(full)) {
|
|
121
|
+
rmSync(full, { recursive: true, force: true });
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
// Remove any broken symlink left from old behavior
|
|
125
|
+
try { rmSync(full, { force: true }); } catch {}
|
|
126
|
+
mkdirSync(dir, { recursive: true });
|
|
127
|
+
cpSync(join(LIBRARY, name), full, { recursive: true });
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
// Symlink-based toggle for global dirs
|
|
93
131
|
if (isLibraryLink(dir, name)) {
|
|
94
132
|
unlinkSync(full);
|
|
95
133
|
return true;
|
|
@@ -114,23 +152,163 @@ function ensureLocalDir(): string {
|
|
|
114
152
|
mkdirSync(dir, { recursive: true });
|
|
115
153
|
localDir = dir;
|
|
116
154
|
localLabel = dir.replace(homedir(), "~");
|
|
117
|
-
colLocal.content = ellipsize(localLabel,
|
|
155
|
+
colLocal.content = ellipsize(localLabel, COL_W - 1);
|
|
118
156
|
refreshAll();
|
|
119
157
|
return dir;
|
|
120
158
|
}
|
|
121
159
|
|
|
160
|
+
function findLocalClaudeDir(): string | null {
|
|
161
|
+
const dir = join(process.cwd(), ".claude/skills");
|
|
162
|
+
if (existsSync(dir)) return dir;
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function ensureLocalClaudeDir(): string {
|
|
167
|
+
if (localClaudeDir) return localClaudeDir;
|
|
168
|
+
const dir = join(process.cwd(), ".claude/skills");
|
|
169
|
+
mkdirSync(dir, { recursive: true });
|
|
170
|
+
localClaudeDir = dir;
|
|
171
|
+
localClaudeLabel = dir.replace(homedir(), "~");
|
|
172
|
+
colLocalClaude.content = ellipsize(localClaudeLabel, COL_W - 1);
|
|
173
|
+
refreshAll();
|
|
174
|
+
return dir;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// ── Migration check ──────────────────────────────────────────────────
|
|
178
|
+
{
|
|
179
|
+
// Only migrate global dirs — project-local dirs use copies, not symlinks
|
|
180
|
+
const dirsToCheck: [string, string][] = [
|
|
181
|
+
[GLOBAL_DIR, "~/.agents/skills"],
|
|
182
|
+
[GLOBAL_CLAUDE_DIR, "~/.claude/skills"],
|
|
183
|
+
];
|
|
184
|
+
|
|
185
|
+
const migrations: { dir: string; label: string; name: string; conflict: boolean }[] = [];
|
|
186
|
+
|
|
187
|
+
for (const [dir, label] of dirsToCheck) {
|
|
188
|
+
try {
|
|
189
|
+
for (const name of readdirSync(dir)) {
|
|
190
|
+
if (!isLibraryLink(dir, name)) {
|
|
191
|
+
migrations.push({ dir, label, name, conflict: existsSync(join(LIBRARY, name)) });
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
} catch {
|
|
195
|
+
// dir doesn't exist
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (migrations.length > 0) {
|
|
200
|
+
console.log("\nFound skills not managed by library:");
|
|
201
|
+
for (const m of migrations) {
|
|
202
|
+
console.log(` ${m.label}/${m.name}${m.conflict ? " (already in library)" : ""}`);
|
|
203
|
+
}
|
|
204
|
+
process.stdout.write(`\nMigrate to ${LIBRARY.replace(homedir(), "~")} and replace with symlinks? [y/N] `);
|
|
205
|
+
|
|
206
|
+
const buf = new Uint8Array(100);
|
|
207
|
+
const n = readSync(0, buf);
|
|
208
|
+
const answer = new TextDecoder().decode(buf.subarray(0, n)).trim().toLowerCase();
|
|
209
|
+
|
|
210
|
+
if (answer === "y" || answer === "yes") {
|
|
211
|
+
let migrated = 0;
|
|
212
|
+
for (const m of migrations) {
|
|
213
|
+
const src = join(m.dir, m.name);
|
|
214
|
+
const dest = join(LIBRARY, m.name);
|
|
215
|
+
if (m.conflict) {
|
|
216
|
+
// Library already has this skill — remove local and symlink to library
|
|
217
|
+
rmSync(src, { recursive: true, force: true });
|
|
218
|
+
symlinkSync(dest, src);
|
|
219
|
+
console.log(` replaced ${m.name} (library version kept)`);
|
|
220
|
+
} else {
|
|
221
|
+
try {
|
|
222
|
+
renameSync(src, dest);
|
|
223
|
+
} catch {
|
|
224
|
+
// cross-device fallback
|
|
225
|
+
cpSync(src, dest, { recursive: true });
|
|
226
|
+
rmSync(src, { recursive: true, force: true });
|
|
227
|
+
}
|
|
228
|
+
symlinkSync(dest, src);
|
|
229
|
+
console.log(` migrated ${m.name}`);
|
|
230
|
+
}
|
|
231
|
+
migrated++;
|
|
232
|
+
}
|
|
233
|
+
console.log(`\n${migrated} migrated\n`);
|
|
234
|
+
} else {
|
|
235
|
+
console.log("Skipping migration.\n");
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ── Local symlink → copy conversion ─────────────────────────────────
|
|
241
|
+
{
|
|
242
|
+
const localDirsToCheck: [string, string][] = [];
|
|
243
|
+
const ld = findLocalDir();
|
|
244
|
+
if (ld) localDirsToCheck.push([ld, ld.replace(homedir(), "~")]);
|
|
245
|
+
const lcd = findLocalClaudeDir();
|
|
246
|
+
if (lcd) localDirsToCheck.push([lcd, lcd.replace(homedir(), "~")]);
|
|
247
|
+
|
|
248
|
+
const localSymlinks: { dir: string; label: string; name: string; target: string }[] = [];
|
|
249
|
+
|
|
250
|
+
for (const [dir, label] of localDirsToCheck) {
|
|
251
|
+
try {
|
|
252
|
+
for (const name of readdirSync(dir)) {
|
|
253
|
+
const full = join(dir, name);
|
|
254
|
+
try {
|
|
255
|
+
if (lstatSync(full).isSymbolicLink()) {
|
|
256
|
+
localSymlinks.push({ dir, label, name, target: readlinkSync(full) });
|
|
257
|
+
}
|
|
258
|
+
} catch {}
|
|
259
|
+
}
|
|
260
|
+
} catch {}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (localSymlinks.length > 0) {
|
|
264
|
+
console.log("\nFound symlinked skills in project-local dirs:");
|
|
265
|
+
for (const s of localSymlinks) {
|
|
266
|
+
console.log(` ${s.label}/${s.name} → ${s.target}`);
|
|
267
|
+
}
|
|
268
|
+
process.stdout.write("\nConvert to local copies? (recommended for portability) [y/N] ");
|
|
269
|
+
|
|
270
|
+
const buf = new Uint8Array(100);
|
|
271
|
+
const n = readSync(0, buf);
|
|
272
|
+
const answer = new TextDecoder().decode(buf.subarray(0, n)).trim().toLowerCase();
|
|
273
|
+
|
|
274
|
+
if (answer === "y" || answer === "yes") {
|
|
275
|
+
let converted = 0;
|
|
276
|
+
for (const s of localSymlinks) {
|
|
277
|
+
const full = join(s.dir, s.name);
|
|
278
|
+
const libPath = join(LIBRARY, s.name);
|
|
279
|
+
unlinkSync(full);
|
|
280
|
+
if (existsSync(libPath)) {
|
|
281
|
+
cpSync(libPath, full, { recursive: true });
|
|
282
|
+
console.log(` converted ${s.name}`);
|
|
283
|
+
converted++;
|
|
284
|
+
} else {
|
|
285
|
+
console.log(` removed ${s.name} (not in library)`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
console.log(`\n${converted} converted\n`);
|
|
289
|
+
} else {
|
|
290
|
+
console.log("Skipping conversion.\n");
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
122
295
|
const allSkills = readdirSync(LIBRARY).sort();
|
|
123
296
|
|
|
124
297
|
// ── State ───────────────────────────────────────────────────────────
|
|
125
298
|
|
|
299
|
+
type ColId = "global" | "globalClaude" | "local" | "localClaude";
|
|
300
|
+
|
|
126
301
|
let cursor = 0; // index into filteredIndices
|
|
127
|
-
let cursorCol:
|
|
302
|
+
let cursorCol: ColId = "global";
|
|
303
|
+
let focusArea: "search" | "grid" = "search";
|
|
128
304
|
let localDir = findLocalDir();
|
|
305
|
+
let localClaudeDir = findLocalClaudeDir();
|
|
129
306
|
let statusTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
130
307
|
let pendingDelete: number | null = null; // allSkills index awaiting confirmation
|
|
131
308
|
const deletedSkills = new Set<number>();
|
|
132
309
|
let searchQuery = "";
|
|
133
310
|
let filteredIndices: number[] = allSkills.map((_, i) => i);
|
|
311
|
+
const COL_ORDER: ColId[] = ["global", "globalClaude", "local", "localClaude"];
|
|
134
312
|
|
|
135
313
|
// ── Colors ──────────────────────────────────────────────────────────
|
|
136
314
|
|
|
@@ -179,15 +357,22 @@ const outer = new BoxRenderable(renderer, {
|
|
|
179
357
|
backgroundColor: C.bg,
|
|
180
358
|
});
|
|
181
359
|
|
|
182
|
-
// Search bar (
|
|
360
|
+
// Search bar (lives inside scrollBox as the first row)
|
|
361
|
+
const searchRow = new BoxRenderable(renderer, {
|
|
362
|
+
id: "search-row",
|
|
363
|
+
flexDirection: "row",
|
|
364
|
+
height: 1,
|
|
365
|
+
width: "100%",
|
|
366
|
+
paddingLeft: 1,
|
|
367
|
+
backgroundColor: C.rowBg,
|
|
368
|
+
});
|
|
183
369
|
const searchBar = new TextRenderable(renderer, {
|
|
184
370
|
id: "search-bar",
|
|
185
371
|
content: "",
|
|
186
372
|
fg: C.search,
|
|
187
|
-
width: "100%",
|
|
188
373
|
height: 1,
|
|
189
|
-
visible: true,
|
|
190
374
|
});
|
|
375
|
+
searchRow.add(searchBar);
|
|
191
376
|
|
|
192
377
|
const colHeaderRow = new BoxRenderable(renderer, {
|
|
193
378
|
id: "col-header-row",
|
|
@@ -196,38 +381,65 @@ const colHeaderRow = new BoxRenderable(renderer, {
|
|
|
196
381
|
paddingLeft: 1,
|
|
197
382
|
});
|
|
198
383
|
|
|
384
|
+
let COL_W = 14;
|
|
385
|
+
let NAME_W = 34;
|
|
386
|
+
|
|
387
|
+
function calcWidths() {
|
|
388
|
+
const w = process.stdout.columns || 80;
|
|
389
|
+
COL_W = Math.max(5, Math.min(14, Math.floor((w - 20) / 4)));
|
|
390
|
+
NAME_W = Math.min(34, Math.max(15, w - COL_W * 4 - 1));
|
|
391
|
+
}
|
|
392
|
+
calcWidths();
|
|
393
|
+
|
|
199
394
|
const colName = new TextRenderable(renderer, {
|
|
200
395
|
id: "col-name",
|
|
201
396
|
content: "Skill",
|
|
202
397
|
fg: C.fgDim,
|
|
203
398
|
attributes: TextAttributes.BOLD,
|
|
204
|
-
width:
|
|
399
|
+
width: NAME_W,
|
|
205
400
|
});
|
|
206
401
|
const colGlobal = new TextRenderable(renderer, {
|
|
207
402
|
id: "col-global",
|
|
208
403
|
content: "~/.agents",
|
|
209
404
|
fg: C.fgDim,
|
|
210
405
|
attributes: TextAttributes.BOLD,
|
|
211
|
-
width:
|
|
406
|
+
width: COL_W,
|
|
407
|
+
});
|
|
408
|
+
const colGlobalClaude = new TextRenderable(renderer, {
|
|
409
|
+
id: "col-global-claude",
|
|
410
|
+
content: "~/.claude",
|
|
411
|
+
fg: C.fgDim,
|
|
412
|
+
attributes: TextAttributes.BOLD,
|
|
413
|
+
width: COL_W,
|
|
212
414
|
});
|
|
213
|
-
let localLabel = localDir ? localDir.replace(homedir(), "~") : ".agents
|
|
214
|
-
const localLabelHeader = ellipsize(localLabel, 20);
|
|
415
|
+
let localLabel = localDir ? localDir.replace(homedir(), "~") : ".agents/skills";
|
|
215
416
|
const colLocal = new TextRenderable(renderer, {
|
|
216
417
|
id: "col-local",
|
|
217
|
-
content:
|
|
418
|
+
content: ellipsize(localLabel, COL_W - 1),
|
|
218
419
|
fg: C.fgDim,
|
|
219
420
|
attributes: TextAttributes.BOLD,
|
|
220
|
-
width:
|
|
421
|
+
width: COL_W,
|
|
422
|
+
});
|
|
423
|
+
let localClaudeLabel = localClaudeDir ? localClaudeDir.replace(homedir(), "~") : ".claude/skills";
|
|
424
|
+
const colLocalClaude = new TextRenderable(renderer, {
|
|
425
|
+
id: "col-local-claude",
|
|
426
|
+
content: ellipsize(localClaudeLabel, COL_W - 1),
|
|
427
|
+
fg: C.fgDim,
|
|
428
|
+
attributes: TextAttributes.BOLD,
|
|
429
|
+
width: COL_W,
|
|
221
430
|
});
|
|
222
431
|
|
|
223
432
|
colHeaderRow.add(colName);
|
|
224
433
|
colHeaderRow.add(colGlobal);
|
|
434
|
+
colHeaderRow.add(colGlobalClaude);
|
|
225
435
|
colHeaderRow.add(colLocal);
|
|
436
|
+
colHeaderRow.add(colLocalClaude);
|
|
226
437
|
|
|
227
438
|
const sep = new TextRenderable(renderer, {
|
|
228
439
|
id: "sep",
|
|
229
|
-
content: "─".repeat(
|
|
440
|
+
content: "─".repeat(200),
|
|
230
441
|
fg: C.border,
|
|
442
|
+
width: "100%",
|
|
231
443
|
height: 1,
|
|
232
444
|
});
|
|
233
445
|
|
|
@@ -241,10 +453,14 @@ type RowRefs = {
|
|
|
241
453
|
row: BoxRenderable;
|
|
242
454
|
nameText: TextRenderable;
|
|
243
455
|
globalText: TextRenderable;
|
|
456
|
+
globalClaudeText: TextRenderable;
|
|
244
457
|
localText: TextRenderable;
|
|
458
|
+
localClaudeText: TextRenderable;
|
|
245
459
|
};
|
|
246
460
|
const rows: RowRefs[] = [];
|
|
247
461
|
|
|
462
|
+
scrollBox.add(searchRow);
|
|
463
|
+
|
|
248
464
|
for (let i = 0; i < allSkills.length; i++) {
|
|
249
465
|
const skill = allSkills[i];
|
|
250
466
|
|
|
@@ -259,9 +475,9 @@ for (let i = 0; i < allSkills.length; i++) {
|
|
|
259
475
|
|
|
260
476
|
const nameText = new TextRenderable(renderer, {
|
|
261
477
|
id: `name-${i}`,
|
|
262
|
-
content: ` ${skill}`,
|
|
478
|
+
content: ` ${ellipsize(skill, NAME_W - 3)}`,
|
|
263
479
|
fg: C.fg,
|
|
264
|
-
width:
|
|
480
|
+
width: NAME_W,
|
|
265
481
|
});
|
|
266
482
|
|
|
267
483
|
const gChecked = isLibraryLink(GLOBAL_DIR, skill);
|
|
@@ -270,35 +486,56 @@ for (let i = 0; i < allSkills.length; i++) {
|
|
|
270
486
|
id: `global-${i}`,
|
|
271
487
|
content: checkboxStr(gChecked, gBlocked),
|
|
272
488
|
fg: checkboxColor(gChecked, gBlocked, false),
|
|
273
|
-
width:
|
|
489
|
+
width: COL_W,
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
const gcChecked = isLibraryLink(GLOBAL_CLAUDE_DIR, skill);
|
|
493
|
+
const gcBlocked = nonLibraryExists(GLOBAL_CLAUDE_DIR, skill);
|
|
494
|
+
const globalClaudeText = new TextRenderable(renderer, {
|
|
495
|
+
id: `global-claude-${i}`,
|
|
496
|
+
content: checkboxStr(gcChecked, gcBlocked),
|
|
497
|
+
fg: checkboxColor(gcChecked, gcBlocked, false),
|
|
498
|
+
width: COL_W,
|
|
274
499
|
});
|
|
275
500
|
|
|
276
|
-
const lChecked = localDir ?
|
|
277
|
-
const lBlocked = localDir ?
|
|
501
|
+
const lChecked = localDir ? isEnabled(localDir, skill) : false;
|
|
502
|
+
const lBlocked = localDir ? isBlocked(localDir, skill) : false;
|
|
278
503
|
const localText = new TextRenderable(renderer, {
|
|
279
504
|
id: `local-${i}`,
|
|
280
505
|
content: localDir ? checkboxStr(lChecked, lBlocked) : " -",
|
|
281
506
|
fg: localDir ? checkboxColor(lChecked, lBlocked, false) : C.fgDim,
|
|
282
|
-
width:
|
|
507
|
+
width: COL_W,
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
const lcChecked = localClaudeDir ? isEnabled(localClaudeDir, skill) : false;
|
|
511
|
+
const lcBlocked = localClaudeDir ? isBlocked(localClaudeDir, skill) : false;
|
|
512
|
+
const localClaudeText = new TextRenderable(renderer, {
|
|
513
|
+
id: `local-claude-${i}`,
|
|
514
|
+
content: localClaudeDir ? checkboxStr(lcChecked, lcBlocked) : " -",
|
|
515
|
+
fg: localClaudeDir ? checkboxColor(lcChecked, lcBlocked, false) : C.fgDim,
|
|
516
|
+
width: COL_W,
|
|
283
517
|
});
|
|
284
518
|
|
|
285
519
|
row.add(nameText);
|
|
286
520
|
row.add(globalText);
|
|
521
|
+
row.add(globalClaudeText);
|
|
287
522
|
row.add(localText);
|
|
523
|
+
row.add(localClaudeText);
|
|
288
524
|
scrollBox.add(row);
|
|
289
|
-
rows.push({ row, nameText, globalText, localText });
|
|
525
|
+
rows.push({ row, nameText, globalText, globalClaudeText, localText, localClaudeText });
|
|
290
526
|
}
|
|
291
527
|
|
|
292
528
|
const footerSep = new TextRenderable(renderer, {
|
|
293
529
|
id: "footer-sep",
|
|
294
|
-
content: "─".repeat(
|
|
530
|
+
content: "─".repeat(200),
|
|
295
531
|
fg: C.border,
|
|
532
|
+
width: "100%",
|
|
296
533
|
height: 1,
|
|
297
534
|
});
|
|
298
535
|
|
|
299
536
|
const footer = new TextRenderable(renderer, {
|
|
300
537
|
id: "footer",
|
|
301
|
-
content: "
|
|
538
|
+
content: " ↑↓ move ←→/tab col enter toggle ^a all ^e edit ^d del / search esc quit",
|
|
302
539
|
fg: C.footer,
|
|
303
540
|
height: 1,
|
|
304
541
|
});
|
|
@@ -310,7 +547,6 @@ const statusLine = new TextRenderable(renderer, {
|
|
|
310
547
|
height: 1,
|
|
311
548
|
});
|
|
312
549
|
|
|
313
|
-
outer.add(searchBar);
|
|
314
550
|
outer.add(colHeaderRow);
|
|
315
551
|
outer.add(sep);
|
|
316
552
|
outer.add(scrollBox);
|
|
@@ -342,7 +578,15 @@ function applyFilter() {
|
|
|
342
578
|
}
|
|
343
579
|
|
|
344
580
|
function updateSearchBar() {
|
|
345
|
-
|
|
581
|
+
if (focusArea === "search") {
|
|
582
|
+
searchBar.content = `▸ ${searchQuery}█`;
|
|
583
|
+
searchBar.fg = C.search;
|
|
584
|
+
searchRow.backgroundColor = C.cursorBg;
|
|
585
|
+
} else {
|
|
586
|
+
searchBar.content = searchQuery ? ` ${searchQuery}` : ` search...`;
|
|
587
|
+
searchBar.fg = C.fgDim;
|
|
588
|
+
searchRow.backgroundColor = C.rowBg;
|
|
589
|
+
}
|
|
346
590
|
}
|
|
347
591
|
|
|
348
592
|
// ── Update display ──────────────────────────────────────────────────
|
|
@@ -355,14 +599,14 @@ function updateRow(i: number) {
|
|
|
355
599
|
const skill = allSkills[i];
|
|
356
600
|
const r = rows[i];
|
|
357
601
|
const ci = currentSkillIndex();
|
|
358
|
-
const isCursor = ci === i;
|
|
602
|
+
const isCursor = ci === i && focusArea === "grid";
|
|
359
603
|
|
|
360
604
|
const visPos = filteredIndices.indexOf(i);
|
|
361
605
|
const baseBg = visPos % 2 === 0 ? C.rowBg : C.rowAltBg;
|
|
362
606
|
r.row.backgroundColor = isCursor ? C.cursorBg : baseBg;
|
|
363
607
|
|
|
364
608
|
const pointer = isCursor ? "▸" : " ";
|
|
365
|
-
r.nameText.content = `${pointer} ${skill}`;
|
|
609
|
+
r.nameText.content = `${pointer} ${ellipsize(skill, NAME_W - 3)}`;
|
|
366
610
|
r.nameText.fg = isCursor ? "#ffffff" : C.fg;
|
|
367
611
|
r.nameText.attributes = isCursor ? TextAttributes.BOLD : TextAttributes.NONE;
|
|
368
612
|
|
|
@@ -374,8 +618,16 @@ function updateRow(i: number) {
|
|
|
374
618
|
r.globalText.bg = gActive ? C.accentBg : undefined;
|
|
375
619
|
r.globalText.attributes = gActive ? TextAttributes.BOLD : TextAttributes.NONE;
|
|
376
620
|
|
|
377
|
-
const
|
|
378
|
-
const
|
|
621
|
+
const gcChecked = isLibraryLink(GLOBAL_CLAUDE_DIR, skill);
|
|
622
|
+
const gcBlocked = nonLibraryExists(GLOBAL_CLAUDE_DIR, skill);
|
|
623
|
+
const gcActive = isCursor && cursorCol === "globalClaude";
|
|
624
|
+
r.globalClaudeText.content = checkboxStr(gcChecked, gcBlocked);
|
|
625
|
+
r.globalClaudeText.fg = checkboxColor(gcChecked, gcBlocked, gcActive);
|
|
626
|
+
r.globalClaudeText.bg = gcActive ? C.accentBg : undefined;
|
|
627
|
+
r.globalClaudeText.attributes = gcActive ? TextAttributes.BOLD : TextAttributes.NONE;
|
|
628
|
+
|
|
629
|
+
const lChecked = localDir ? isEnabled(localDir, skill) : false;
|
|
630
|
+
const lBlocked = localDir ? isBlocked(localDir, skill) : false;
|
|
379
631
|
const lActive = isCursor && cursorCol === "local";
|
|
380
632
|
if (localDir) {
|
|
381
633
|
r.localText.content = checkboxStr(lChecked, lBlocked);
|
|
@@ -387,6 +639,20 @@ function updateRow(i: number) {
|
|
|
387
639
|
r.localText.fg = C.fgDim;
|
|
388
640
|
r.localText.bg = lActive ? C.accentBg : undefined;
|
|
389
641
|
}
|
|
642
|
+
|
|
643
|
+
const lcChecked = localClaudeDir ? isEnabled(localClaudeDir, skill) : false;
|
|
644
|
+
const lcBlocked = localClaudeDir ? isBlocked(localClaudeDir, skill) : false;
|
|
645
|
+
const lcActive = isCursor && cursorCol === "localClaude";
|
|
646
|
+
if (localClaudeDir) {
|
|
647
|
+
r.localClaudeText.content = checkboxStr(lcChecked, lcBlocked);
|
|
648
|
+
r.localClaudeText.fg = checkboxColor(lcChecked, lcBlocked, lcActive);
|
|
649
|
+
r.localClaudeText.bg = lcActive ? C.accentBg : undefined;
|
|
650
|
+
r.localClaudeText.attributes = lcActive ? TextAttributes.BOLD : TextAttributes.NONE;
|
|
651
|
+
} else {
|
|
652
|
+
r.localClaudeText.content = " -";
|
|
653
|
+
r.localClaudeText.fg = C.fgDim;
|
|
654
|
+
r.localClaudeText.bg = lcActive ? C.accentBg : undefined;
|
|
655
|
+
}
|
|
390
656
|
}
|
|
391
657
|
|
|
392
658
|
function setStatus(msg: string, color: string) {
|
|
@@ -398,6 +664,30 @@ function setStatus(msg: string, color: string) {
|
|
|
398
664
|
}, 3000);
|
|
399
665
|
}
|
|
400
666
|
|
|
667
|
+
function relayout() {
|
|
668
|
+
calcWidths();
|
|
669
|
+
colName.width = NAME_W;
|
|
670
|
+
colGlobal.width = COL_W;
|
|
671
|
+
colGlobalClaude.width = COL_W;
|
|
672
|
+
colLocal.width = COL_W;
|
|
673
|
+
colLocal.content = ellipsize(localLabel, COL_W - 1);
|
|
674
|
+
colLocalClaude.width = COL_W;
|
|
675
|
+
colLocalClaude.content = ellipsize(localClaudeLabel, COL_W - 1);
|
|
676
|
+
for (let i = 0; i < allSkills.length; i++) {
|
|
677
|
+
const r = rows[i];
|
|
678
|
+
r.nameText.width = NAME_W;
|
|
679
|
+
r.globalText.width = COL_W;
|
|
680
|
+
r.globalClaudeText.width = COL_W;
|
|
681
|
+
r.localText.width = COL_W;
|
|
682
|
+
r.localClaudeText.width = COL_W;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
process.stdout.on("resize", () => {
|
|
687
|
+
relayout();
|
|
688
|
+
refreshAll();
|
|
689
|
+
});
|
|
690
|
+
|
|
401
691
|
function refreshAll() {
|
|
402
692
|
for (const i of filteredIndices) updateRow(i);
|
|
403
693
|
updateSearchBar();
|
|
@@ -408,18 +698,30 @@ refreshAll();
|
|
|
408
698
|
// ── Scrolling helper ────────────────────────────────────────────────
|
|
409
699
|
|
|
410
700
|
function ensureVisible() {
|
|
411
|
-
|
|
701
|
+
if (focusArea === "search") {
|
|
702
|
+
scrollBox.scrollTo(0);
|
|
703
|
+
} else {
|
|
704
|
+
// +1 offset because searchRow is child 0 in scrollBox
|
|
705
|
+
scrollBox.scrollTo(Math.max(0, cursor + 1 - 2));
|
|
706
|
+
}
|
|
412
707
|
}
|
|
413
708
|
|
|
414
709
|
// ── Toggle all in column ────────────────────────────────────────────
|
|
415
710
|
|
|
416
|
-
function toggleAllColumn(col:
|
|
417
|
-
|
|
711
|
+
function toggleAllColumn(col: ColId) {
|
|
712
|
+
let dir: string;
|
|
713
|
+
let dirLabel: string;
|
|
714
|
+
switch (col) {
|
|
715
|
+
case "global": dir = GLOBAL_DIR; dirLabel = "~/.agents"; break;
|
|
716
|
+
case "globalClaude": dir = GLOBAL_CLAUDE_DIR; dirLabel = "~/.claude"; break;
|
|
717
|
+
case "local": dir = ensureLocalDir(); dirLabel = localLabel; break;
|
|
718
|
+
case "localClaude": dir = ensureLocalClaudeDir(); dirLabel = localClaudeLabel; break;
|
|
719
|
+
}
|
|
418
720
|
|
|
419
721
|
// Determine intent: if majority of visible skills are linked, unlink all; otherwise link all
|
|
420
722
|
let linkedCount = 0;
|
|
421
723
|
for (const i of filteredIndices) {
|
|
422
|
-
if (
|
|
724
|
+
if (isEnabled(dir, allSkills[i])) linkedCount++;
|
|
423
725
|
}
|
|
424
726
|
const shouldLink = linkedCount <= filteredIndices.length / 2;
|
|
425
727
|
let changed = 0;
|
|
@@ -427,7 +729,7 @@ function toggleAllColumn(col: "global" | "local") {
|
|
|
427
729
|
|
|
428
730
|
for (const i of filteredIndices) {
|
|
429
731
|
const skill = allSkills[i];
|
|
430
|
-
const isLinked =
|
|
732
|
+
const isLinked = isEnabled(dir, skill);
|
|
431
733
|
if (shouldLink && !isLinked) {
|
|
432
734
|
if (toggle(dir, skill)) changed++;
|
|
433
735
|
else skipped++;
|
|
@@ -436,7 +738,6 @@ function toggleAllColumn(col: "global" | "local") {
|
|
|
436
738
|
}
|
|
437
739
|
}
|
|
438
740
|
|
|
439
|
-
const dirLabel = col === "global" ? "~/.agents" : localLabel;
|
|
440
741
|
const action = shouldLink ? "linked" : "unlinked";
|
|
441
742
|
let msg = `${action} ${changed} skills in ${dirLabel}`;
|
|
442
743
|
if (skipped) msg += ` (${skipped} skipped)`;
|
|
@@ -474,9 +775,11 @@ function cancelPendingDelete() {
|
|
|
474
775
|
function deleteSkill(idx: number) {
|
|
475
776
|
const skill = allSkills[idx];
|
|
476
777
|
|
|
477
|
-
// Remove symlinks
|
|
778
|
+
// Remove from all skill dirs (symlinks for global, copies for local)
|
|
478
779
|
if (isLibraryLink(GLOBAL_DIR, skill)) unlinkSync(join(GLOBAL_DIR, skill));
|
|
479
|
-
if (
|
|
780
|
+
if (isLibraryLink(GLOBAL_CLAUDE_DIR, skill)) unlinkSync(join(GLOBAL_CLAUDE_DIR, skill));
|
|
781
|
+
if (localDir && existsSync(join(localDir, skill))) rmSync(join(localDir, skill), { recursive: true, force: true });
|
|
782
|
+
if (localClaudeDir && existsSync(join(localClaudeDir, skill))) rmSync(join(localClaudeDir, skill), { recursive: true, force: true });
|
|
480
783
|
|
|
481
784
|
// Remove from library
|
|
482
785
|
rmSync(join(LIBRARY, skill), { recursive: true, force: true });
|
|
@@ -494,6 +797,25 @@ function deleteSkill(idx: number) {
|
|
|
494
797
|
|
|
495
798
|
// ── Key handler ─────────────────────────────────────────────────────
|
|
496
799
|
|
|
800
|
+
function dirForCol(col: ColId): { dir: string; label: string } {
|
|
801
|
+
switch (col) {
|
|
802
|
+
case "global": return { dir: GLOBAL_DIR, label: "~/.agents/skills" };
|
|
803
|
+
case "globalClaude": return { dir: GLOBAL_CLAUDE_DIR, label: "~/.claude/skills" };
|
|
804
|
+
case "local": return { dir: ensureLocalDir(), label: localLabel };
|
|
805
|
+
case "localClaude": return { dir: ensureLocalClaudeDir(), label: localClaudeLabel };
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
function colNext(col: ColId): ColId {
|
|
810
|
+
const i = COL_ORDER.indexOf(col);
|
|
811
|
+
return COL_ORDER[(i + 1) % COL_ORDER.length];
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
function colPrev(col: ColId): ColId {
|
|
815
|
+
const i = COL_ORDER.indexOf(col);
|
|
816
|
+
return COL_ORDER[(i - 1 + COL_ORDER.length) % COL_ORDER.length];
|
|
817
|
+
}
|
|
818
|
+
|
|
497
819
|
renderer.keyInput.on("keypress", (key: KeyEvent) => {
|
|
498
820
|
// ── Delete confirmation mode ──
|
|
499
821
|
if (pendingDelete !== null) {
|
|
@@ -508,13 +830,17 @@ renderer.keyInput.on("keypress", (key: KeyEvent) => {
|
|
|
508
830
|
|
|
509
831
|
const prevIdx = currentSkillIndex();
|
|
510
832
|
|
|
511
|
-
// ── Escape
|
|
833
|
+
// ── Escape ──
|
|
512
834
|
if (key.name === "escape") {
|
|
513
|
-
if (searchQuery) {
|
|
835
|
+
if (focusArea === "search" && searchQuery) {
|
|
514
836
|
searchQuery = "";
|
|
515
837
|
applyFilter();
|
|
516
838
|
refreshAll();
|
|
517
839
|
ensureVisible();
|
|
840
|
+
} else if (focusArea === "search") {
|
|
841
|
+
focusArea = "grid";
|
|
842
|
+
refreshAll();
|
|
843
|
+
ensureVisible();
|
|
518
844
|
} else {
|
|
519
845
|
renderer.destroy();
|
|
520
846
|
process.exit(0);
|
|
@@ -522,18 +848,41 @@ renderer.keyInput.on("keypress", (key: KeyEvent) => {
|
|
|
522
848
|
return;
|
|
523
849
|
}
|
|
524
850
|
|
|
525
|
-
// ──
|
|
526
|
-
if (
|
|
527
|
-
if (
|
|
528
|
-
|
|
851
|
+
// ── Search-focused input ──
|
|
852
|
+
if (focusArea === "search") {
|
|
853
|
+
if (key.name === "backspace") {
|
|
854
|
+
if (searchQuery) {
|
|
855
|
+
searchQuery = searchQuery.slice(0, -1);
|
|
856
|
+
applyFilter();
|
|
857
|
+
cursor = 0;
|
|
858
|
+
refreshAll();
|
|
859
|
+
ensureVisible();
|
|
860
|
+
}
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
if (key.name === "down" || key.name === "return") {
|
|
864
|
+
focusArea = "grid";
|
|
865
|
+
refreshAll();
|
|
866
|
+
ensureVisible();
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
// Printable character → append to search
|
|
870
|
+
if (key.sequence && key.sequence.length === 1 && !key.ctrl && !key.meta) {
|
|
871
|
+
searchQuery += key.sequence;
|
|
529
872
|
applyFilter();
|
|
530
873
|
cursor = 0;
|
|
531
874
|
refreshAll();
|
|
532
875
|
ensureVisible();
|
|
876
|
+
return;
|
|
533
877
|
}
|
|
534
878
|
return;
|
|
535
879
|
}
|
|
536
880
|
|
|
881
|
+
// ── Grid-focused input ──
|
|
882
|
+
|
|
883
|
+
// ── Backspace in grid: do nothing ──
|
|
884
|
+
if (key.name === "backspace") return;
|
|
885
|
+
|
|
537
886
|
// ── Ctrl combos ──
|
|
538
887
|
if (key.ctrl) {
|
|
539
888
|
switch (key.name) {
|
|
@@ -559,22 +908,37 @@ renderer.keyInput.on("keypress", (key: KeyEvent) => {
|
|
|
559
908
|
return;
|
|
560
909
|
}
|
|
561
910
|
|
|
562
|
-
// ──
|
|
911
|
+
// ── "/" to focus search (vim-style) ──
|
|
912
|
+
if (key.sequence === "/") {
|
|
913
|
+
focusArea = "search";
|
|
914
|
+
refreshAll();
|
|
915
|
+
ensureVisible();
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
// ── Navigation & actions ──
|
|
563
920
|
switch (key.name) {
|
|
564
921
|
case "down":
|
|
565
922
|
if (cursor < filteredIndices.length - 1) cursor++;
|
|
566
923
|
break;
|
|
567
924
|
case "up":
|
|
568
|
-
if (cursor > 0)
|
|
925
|
+
if (cursor > 0) {
|
|
926
|
+
cursor--;
|
|
927
|
+
} else {
|
|
928
|
+
// At top of grid → move to search
|
|
929
|
+
focusArea = "search";
|
|
930
|
+
refreshAll();
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
569
933
|
break;
|
|
570
934
|
case "left":
|
|
571
|
-
cursorCol =
|
|
935
|
+
cursorCol = colPrev(cursorCol);
|
|
572
936
|
break;
|
|
573
937
|
case "right":
|
|
574
|
-
cursorCol =
|
|
938
|
+
cursorCol = colNext(cursorCol);
|
|
575
939
|
break;
|
|
576
940
|
case "tab":
|
|
577
|
-
cursorCol = cursorCol
|
|
941
|
+
cursorCol = colNext(cursorCol);
|
|
578
942
|
break;
|
|
579
943
|
case "pagedown":
|
|
580
944
|
cursor = Math.min(filteredIndices.length - 1, cursor + 10);
|
|
@@ -588,46 +952,25 @@ renderer.keyInput.on("keypress", (key: KeyEvent) => {
|
|
|
588
952
|
case "end":
|
|
589
953
|
cursor = Math.max(0, filteredIndices.length - 1);
|
|
590
954
|
break;
|
|
955
|
+
case "space":
|
|
591
956
|
case "return": {
|
|
592
957
|
const idx = currentSkillIndex();
|
|
593
958
|
if (idx === null) break;
|
|
594
959
|
const skill = allSkills[idx];
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
} else {
|
|
604
|
-
setStatus(`⚠ ${skill}: non-library file exists in ~/.agents/skills, skipped`, C.warning);
|
|
605
|
-
}
|
|
960
|
+
const { dir, label } = dirForCol(cursorCol);
|
|
961
|
+
const ok = toggle(dir, skill);
|
|
962
|
+
if (ok) {
|
|
963
|
+
const linked = isEnabled(dir, skill);
|
|
964
|
+
setStatus(
|
|
965
|
+
linked ? `✓ ${skill} → ${label}` : `✗ ${skill} removed from ${label}`,
|
|
966
|
+
linked ? C.statusOk : C.statusErr
|
|
967
|
+
);
|
|
606
968
|
} else {
|
|
607
|
-
|
|
608
|
-
const ok = toggle(dir, skill);
|
|
609
|
-
if (ok) {
|
|
610
|
-
const linked = isLibraryLink(localDir, skill);
|
|
611
|
-
setStatus(
|
|
612
|
-
linked ? `✓ ${skill} → ${localLabel}` : `✗ ${skill} removed from ${localLabel}`,
|
|
613
|
-
linked ? C.statusOk : C.statusErr
|
|
614
|
-
);
|
|
615
|
-
} else {
|
|
616
|
-
setStatus(`⚠ ${skill}: non-library file exists in ${localLabel}, skipped`, C.warning);
|
|
617
|
-
}
|
|
969
|
+
setStatus(`⚠ ${skill}: non-library file exists in ${label}, skipped`, C.warning);
|
|
618
970
|
}
|
|
619
971
|
break;
|
|
620
972
|
}
|
|
621
973
|
default:
|
|
622
|
-
// ── Printable character → search input ──
|
|
623
|
-
if (key.sequence && key.sequence.length === 1 && !key.meta) {
|
|
624
|
-
searchQuery += key.sequence;
|
|
625
|
-
applyFilter();
|
|
626
|
-
cursor = 0;
|
|
627
|
-
refreshAll();
|
|
628
|
-
ensureVisible();
|
|
629
|
-
return;
|
|
630
|
-
}
|
|
631
974
|
return;
|
|
632
975
|
}
|
|
633
976
|
|