@duckmind/dm-windows-x64 0.63.0 → 0.63.6

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.
Files changed (101) hide show
  1. package/dm.exe +0 -0
  2. package/extensions/.dm-extensions.json +200 -4
  3. package/extensions/dm-context/package.json +1 -1
  4. package/extensions/dm-context/skills/context-management/SKILL.md +173 -223
  5. package/extensions/dm-context/skills/context-management/references/development-and-troubleshooting.md +75 -0
  6. package/extensions/dm-context/skills/context-management/references/interleaved-async-work.md +143 -0
  7. package/extensions/dm-context/skills/context-management/references/planning-and-execution.md +81 -0
  8. package/extensions/dm-context/skills/context-management/references/repeated-items-and-batch-work.md +69 -0
  9. package/extensions/dm-context/skills/context-management/references/retry-branch-and-pivot.md +80 -0
  10. package/extensions/dm-context/skills/context-management/references/search-research-and-reading.md +103 -0
  11. package/extensions/dm-context/skills/context-management/references/task-switching-and-cleanup.md +73 -0
  12. package/extensions/dm-context/src/context.js +3 -2
  13. package/extensions/dm-context/src/index.js +168 -84
  14. package/extensions/dm-skills-manager/THIRD_PARTY_NOTICES.md +27 -0
  15. package/extensions/dm-skills-manager/extensions/skills-manager/components.js +265 -0
  16. package/extensions/dm-skills-manager/extensions/skills-manager/constants.js +31 -0
  17. package/extensions/dm-skills-manager/extensions/skills-manager/creation-fallback.js +20 -0
  18. package/extensions/dm-skills-manager/extensions/skills-manager/creation.js +145 -0
  19. package/extensions/dm-skills-manager/extensions/skills-manager/dialog.js +738 -0
  20. package/extensions/dm-skills-manager/extensions/skills-manager/dm-ai-compat.js +15 -0
  21. package/extensions/dm-skills-manager/extensions/skills-manager/format.js +125 -0
  22. package/extensions/dm-skills-manager/extensions/skills-manager/glyphs.js +142 -0
  23. package/extensions/dm-skills-manager/extensions/skills-manager/layout.js +93 -0
  24. package/extensions/dm-skills-manager/extensions/skills-manager/paths.js +76 -0
  25. package/extensions/dm-skills-manager/extensions/skills-manager/registry.js +95 -0
  26. package/extensions/dm-skills-manager/extensions/skills-manager/settings.js +93 -0
  27. package/extensions/dm-skills-manager/extensions/skills-manager/startup.js +32 -0
  28. package/extensions/dm-skills-manager/extensions/skills-manager/toggle.js +54 -0
  29. package/extensions/dm-skills-manager/extensions/skills-manager/types.js +14 -0
  30. package/extensions/dm-skills-manager/extensions/skills-manager/ui.js +121 -0
  31. package/extensions/dm-skills-manager/extensions/skills-manager.js +111 -0
  32. package/extensions/dm-skills-manager/package.json +121 -0
  33. package/extensions/dm-web-access/SECURITY.md +5 -0
  34. package/extensions/dm-web-access/activity.js +65 -0
  35. package/extensions/dm-web-access/anysearch.js +158 -0
  36. package/extensions/dm-web-access/auth-fetch.js +131 -0
  37. package/extensions/dm-web-access/bocha.js +214 -0
  38. package/extensions/dm-web-access/brave.js +196 -0
  39. package/extensions/dm-web-access/brightdata-unlocker.js +202 -0
  40. package/extensions/dm-web-access/brightdata.js +334 -0
  41. package/extensions/dm-web-access/chrome-cookies.js +627 -0
  42. package/extensions/dm-web-access/content-find.js +114 -0
  43. package/extensions/dm-web-access/credential-source.js +150 -0
  44. package/extensions/dm-web-access/curator-page.js +3559 -0
  45. package/extensions/dm-web-access/curator-server.js +691 -0
  46. package/extensions/dm-web-access/data-uri-sanitize.js +312 -0
  47. package/extensions/dm-web-access/datalab-pdf-extract.js +346 -0
  48. package/extensions/dm-web-access/declared-web-links.js +167 -0
  49. package/extensions/dm-web-access/dm-web-fetch-demo.mp4 +0 -0
  50. package/extensions/dm-web-access/duckduckgo.js +118 -0
  51. package/extensions/dm-web-access/exa.js +401 -0
  52. package/extensions/dm-web-access/extract.js +1220 -0
  53. package/extensions/dm-web-access/feature-config.js +24 -0
  54. package/extensions/dm-web-access/fetch-params.js +81 -0
  55. package/extensions/dm-web-access/firecrawl.js +378 -0
  56. package/extensions/dm-web-access/gemini-adc.js +241 -0
  57. package/extensions/dm-web-access/gemini-api.js +258 -0
  58. package/extensions/dm-web-access/gemini-pdf-extract.js +74 -0
  59. package/extensions/dm-web-access/gemini-search.js +889 -0
  60. package/extensions/dm-web-access/gemini-url-context.js +97 -0
  61. package/extensions/dm-web-access/gemini-web-config.js +84 -0
  62. package/extensions/dm-web-access/gemini-web.js +351 -0
  63. package/extensions/dm-web-access/github-api.js +166 -0
  64. package/extensions/dm-web-access/github-extract.js +991 -0
  65. package/extensions/dm-web-access/github-issue-pr.js +750 -0
  66. package/extensions/dm-web-access/index.js +3117 -0
  67. package/extensions/dm-web-access/jina-search.js +242 -0
  68. package/extensions/dm-web-access/kagi.js +255 -0
  69. package/extensions/dm-web-access/kimi-search.js +214 -0
  70. package/extensions/dm-web-access/ollama.js +209 -0
  71. package/extensions/dm-web-access/openai-search.js +510 -0
  72. package/extensions/dm-web-access/package.json +34 -0
  73. package/extensions/dm-web-access/page-query.js +124 -0
  74. package/extensions/dm-web-access/parallel-mcp.js +223 -0
  75. package/extensions/dm-web-access/parallel.js +345 -0
  76. package/extensions/dm-web-access/pdf-extract.js +257 -0
  77. package/extensions/dm-web-access/perplexity.js +151 -0
  78. package/extensions/dm-web-access/querit.js +327 -0
  79. package/extensions/dm-web-access/query-rewrite.js +40 -0
  80. package/extensions/dm-web-access/render-search-error.js +80 -0
  81. package/extensions/dm-web-access/rsc-extract.js +347 -0
  82. package/extensions/dm-web-access/search1api.js +245 -0
  83. package/extensions/dm-web-access/searchinfinity.js +221 -0
  84. package/extensions/dm-web-access/searxng.js +223 -0
  85. package/extensions/dm-web-access/serpbase.js +205 -0
  86. package/extensions/dm-web-access/serpdive.js +238 -0
  87. package/extensions/dm-web-access/serper.js +200 -0
  88. package/extensions/dm-web-access/source-check.js +198 -0
  89. package/extensions/dm-web-access/ssrf-protection.js +436 -0
  90. package/extensions/dm-web-access/storage.js +451 -0
  91. package/extensions/dm-web-access/summary-model-scope.js +83 -0
  92. package/extensions/dm-web-access/summary-review.js +364 -0
  93. package/extensions/dm-web-access/tavily.js +199 -0
  94. package/extensions/dm-web-access/tinyfish.js +325 -0
  95. package/extensions/dm-web-access/utils.js +476 -0
  96. package/extensions/dm-web-access/valyu.js +189 -0
  97. package/extensions/dm-web-access/video-extract.js +336 -0
  98. package/extensions/dm-web-access/xai-search.js +285 -0
  99. package/extensions/dm-web-access/xcrawl.js +221 -0
  100. package/extensions/dm-web-access/youtube-extract.js +279 -0
  101. package/package.json +9 -1
@@ -0,0 +1,738 @@
1
+ import { writeFileSync } from "node:fs";
2
+ import {
3
+ Container,
4
+ Editor,
5
+ Input,
6
+ Key,
7
+ matchesKey,
8
+ Spacer,
9
+ Text,
10
+ wrapTextWithAnsi
11
+ } from "@duckmind/dm-tui";
12
+ import {
13
+ ListLineText,
14
+ PrefixedEditor,
15
+ ScrollableSkillPreview,
16
+ SearchInputLine,
17
+ SingleLineText,
18
+ SkillEditorView
19
+ } from "./components.js";
20
+ import { DEFAULT_LIST_ROWS, DEFAULT_POPUP_MAX_HEIGHT, DEFAULT_POPUP_WIDTH } from "./constants.js";
21
+ import { renameSkillEntry } from "./creation.js";
22
+ import {
23
+ buildEditableSkillDocument,
24
+ normalizeSkillName,
25
+ parseEditableSkillDocument,
26
+ readSkillDocument,
27
+ toUpdatedSkill
28
+ } from "./format.js";
29
+ import { normalizeListRows, responsiveBrowsePageSelection, responsiveBrowseWindow, sanitizePopupMaxHeight } from "./layout.js";
30
+ import { isDeletableSkill, skillStorageTarget } from "./registry.js";
31
+ import { settingNumber, settingOverlaySize, settingString } from "./settings.js";
32
+ import {
33
+ acquirekendexModalLock,
34
+ getEditorTheme,
35
+ packageLabel,
36
+ renderCenteredDialog,
37
+ renderFrame,
38
+ scopeLabel,
39
+ skillEntityTitle,
40
+ skillKeyHints,
41
+ skillSectionTitle,
42
+ toneText
43
+ } from "./ui.js";
44
+ import {
45
+ CREATE_STEPS
46
+ } from "./types.js";
47
+
48
+ class SkillsManagerDialog {
49
+ ctx;
50
+ theme;
51
+ tui;
52
+ done;
53
+ options;
54
+ requestRender;
55
+ mode = "browse";
56
+ _focused = false;
57
+ registry;
58
+ filteredSkills = [];
59
+ selectedIndex;
60
+ browseQuery;
61
+ browseInput = new Input;
62
+ descriptionEditor;
63
+ renameInput = new Input;
64
+ createStepIndex = 0;
65
+ createValues = { name: "", description: "" };
66
+ createLocation;
67
+ submittedDescriptionValue;
68
+ createError;
69
+ previewSkillPath;
70
+ preview;
71
+ editorView;
72
+ renameError;
73
+ deleteSkillPath;
74
+ deleteReturnMode = "browse";
75
+ generationAbortController;
76
+ generationRunId = 0;
77
+ configuredListRows;
78
+ popupMaxHeight;
79
+ constructor(ctx, registry, theme, tui, done, options, requestRender, initialSelectedIndex = 0, initialQuery = "") {
80
+ this.ctx = ctx;
81
+ this.theme = theme;
82
+ this.tui = tui;
83
+ this.done = done;
84
+ this.options = options;
85
+ this.requestRender = requestRender;
86
+ this.registry = registry;
87
+ this.selectedIndex = Math.max(0, initialSelectedIndex);
88
+ this.browseQuery = initialQuery;
89
+ this.browseInput.setValue(initialQuery);
90
+ this.createLocation = settingString("defaultCreateLocation", "project", ctx.cwd) === "global" ? "global" : "project";
91
+ this.configuredListRows = normalizeListRows(settingNumber("listRows", DEFAULT_LIST_ROWS, ctx.cwd));
92
+ this.popupMaxHeight = sanitizePopupMaxHeight(settingOverlaySize("popupMaxHeight", DEFAULT_POPUP_MAX_HEIGHT, ctx.cwd));
93
+ this.descriptionEditor = new Editor(tui, { borderColor: (text) => " ".repeat(text.length), selectList: getEditorTheme(theme).selectList });
94
+ this.descriptionEditor.onSubmit = (text) => {
95
+ this.submittedDescriptionValue = text;
96
+ this.advanceCreate();
97
+ };
98
+ this.renameInput.onSubmit = (value) => {
99
+ this.submitRename(value);
100
+ };
101
+ this.refreshBrowseList();
102
+ }
103
+ get focused() {
104
+ return this._focused;
105
+ }
106
+ set focused(value) {
107
+ this._focused = value;
108
+ this.syncFocus();
109
+ }
110
+ invalidate() {
111
+ this.browseInput.invalidate();
112
+ this.descriptionEditor.invalidate();
113
+ this.renameInput.invalidate();
114
+ this.preview?.invalidate();
115
+ this.editorView?.invalidate();
116
+ }
117
+ syncFocus() {
118
+ this.browseInput.focused = this._focused && (this.mode === "browse" || this.mode === "create" && this.currentCreateStep.id === "name");
119
+ this.descriptionEditor.focused = this._focused && this.mode === "create" && this.currentCreateStep.id === "description";
120
+ this.renameInput.focused = this._focused && this.mode === "rename";
121
+ if (this.editorView)
122
+ this.editorView.focused = this._focused && this.mode === "edit";
123
+ }
124
+ searchableText(skill) {
125
+ return [skill.name, skill.description, scopeLabel(skill), skill.origin, skill.source, skill.path, skill.baseDir ?? ""].join(" ").toLowerCase();
126
+ }
127
+ filterSkills(query) {
128
+ const trimmed = query.trim().toLowerCase();
129
+ if (!trimmed)
130
+ return this.registry.allSkills;
131
+ const tokens = trimmed.split(/\s+/).filter(Boolean);
132
+ return this.registry.allSkills.filter((skill) => tokens.every((token) => this.searchableText(skill).includes(token)));
133
+ }
134
+ orderBrowseSkills(skills) {
135
+ const own = skills.filter((skill) => isDeletableSkill(skill));
136
+ const library = skills.filter((skill) => !isDeletableSkill(skill));
137
+ return [...own, ...library];
138
+ }
139
+ refreshBrowseList(preferredPath) {
140
+ const currentPath = preferredPath ?? this.getSelectedSkill()?.path;
141
+ this.filteredSkills = this.orderBrowseSkills(this.filterSkills(this.browseQuery));
142
+ if (currentPath) {
143
+ const nextIndex = this.filteredSkills.findIndex((skill) => skill.path === currentPath);
144
+ if (nextIndex >= 0) {
145
+ this.selectedIndex = nextIndex + 1;
146
+ return;
147
+ }
148
+ }
149
+ this.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.filteredSkills.length));
150
+ }
151
+ getSelectedSkill() {
152
+ return this.selectedIndex === 0 ? undefined : this.filteredSkills[this.selectedIndex - 1];
153
+ }
154
+ getCurrentSkill() {
155
+ return this.previewSkillPath ? this.registry.allSkills.find((skill) => skill.path === this.previewSkillPath) : undefined;
156
+ }
157
+ get currentCreateStep() {
158
+ return CREATE_STEPS[this.createStepIndex];
159
+ }
160
+ enterCreateMode() {
161
+ this.mode = "create";
162
+ this.createStepIndex = 0;
163
+ this.createError = undefined;
164
+ this.syncCreateInput();
165
+ this.syncFocus();
166
+ this.requestRender();
167
+ }
168
+ exitToBrowse(preferredPath) {
169
+ this.mode = "browse";
170
+ this.createError = undefined;
171
+ this.renameError = undefined;
172
+ this.previewSkillPath = undefined;
173
+ this.preview = undefined;
174
+ this.editorView = undefined;
175
+ this.deleteSkillPath = undefined;
176
+ this.browseInput.setValue(this.browseQuery);
177
+ this.refreshBrowseList(preferredPath);
178
+ this.syncFocus();
179
+ this.requestRender();
180
+ }
181
+ openPreview(skill) {
182
+ this.previewSkillPath = skill.path;
183
+ this.preview = new ScrollableSkillPreview(skill, this.theme, () => this.tui.terminal.rows);
184
+ this.mode = "preview";
185
+ this.syncFocus();
186
+ this.requestRender();
187
+ }
188
+ openDeleteConfirm(skill, returnMode) {
189
+ this.deleteSkillPath = skill.path;
190
+ this.deleteReturnMode = returnMode;
191
+ this.mode = "delete-confirm";
192
+ this.syncFocus();
193
+ this.requestRender();
194
+ }
195
+ openEditor() {
196
+ const skill = this.getCurrentSkill();
197
+ if (!skill || !isDeletableSkill(skill))
198
+ return;
199
+ this.editorView = new SkillEditorView(skill, this.theme, this.tui, buildEditableSkillDocument(skill, readSkillDocument(skill)), (value) => {
200
+ this.saveEditedSkill(value);
201
+ }, () => this.closeEditor());
202
+ this.mode = "edit";
203
+ this.syncFocus();
204
+ this.requestRender();
205
+ }
206
+ closeEditor() {
207
+ this.editorView = undefined;
208
+ this.mode = "preview";
209
+ this.syncFocus();
210
+ this.requestRender();
211
+ }
212
+ openRenameDialog() {
213
+ const skill = this.getCurrentSkill();
214
+ if (!skill || !isDeletableSkill(skill))
215
+ return;
216
+ this.renameError = undefined;
217
+ this.renameInput.setValue(skill.name);
218
+ this.mode = "rename";
219
+ this.syncFocus();
220
+ this.requestRender();
221
+ }
222
+ closeRenameDialog() {
223
+ this.renameError = undefined;
224
+ this.mode = "preview";
225
+ this.syncFocus();
226
+ this.requestRender();
227
+ }
228
+ syncCreateInput() {
229
+ const step = this.currentCreateStep;
230
+ if (step.id === "name")
231
+ this.browseInput.setValue(this.createValues.name);
232
+ if (step.id === "description") {
233
+ this.submittedDescriptionValue = undefined;
234
+ this.descriptionEditor.setText(this.createValues.description);
235
+ }
236
+ }
237
+ persistCreateInput() {
238
+ const step = this.currentCreateStep;
239
+ if (step.id === "name")
240
+ this.createValues.name = this.browseInput.getValue();
241
+ else if (step.id === "description") {
242
+ this.createValues.description = this.submittedDescriptionValue !== undefined ? this.submittedDescriptionValue : this.descriptionEditor.getText();
243
+ this.submittedDescriptionValue = undefined;
244
+ }
245
+ }
246
+ validateCreateStep() {
247
+ this.persistCreateInput();
248
+ const step = this.currentCreateStep;
249
+ if (step.kind === "text" && !step.optional) {
250
+ const value = this.createValues[step.id].trim();
251
+ if (!value) {
252
+ this.createError = `${step.title} is required.`;
253
+ return false;
254
+ }
255
+ if (step.id === "name" && !normalizeSkillName(value)) {
256
+ this.createError = "Name must contain letters, numbers, or hyphens.";
257
+ return false;
258
+ }
259
+ }
260
+ this.createError = undefined;
261
+ return true;
262
+ }
263
+ goToPreviousCreateStep() {
264
+ this.persistCreateInput();
265
+ if (this.createStepIndex > 0) {
266
+ this.createError = undefined;
267
+ this.createStepIndex -= 1;
268
+ this.syncCreateInput();
269
+ this.syncFocus();
270
+ }
271
+ }
272
+ async advanceCreate() {
273
+ if (!this.validateCreateStep())
274
+ return;
275
+ if (this.createStepIndex >= CREATE_STEPS.length - 1)
276
+ await this.submitCreate();
277
+ else {
278
+ this.createStepIndex += 1;
279
+ this.syncCreateInput();
280
+ this.syncFocus();
281
+ }
282
+ this.requestRender();
283
+ }
284
+ async submitCreate() {
285
+ const name = normalizeSkillName(this.createValues.name);
286
+ if (!name) {
287
+ this.createStepIndex = 0;
288
+ this.syncCreateInput();
289
+ this.createError = "Name is required.";
290
+ return;
291
+ }
292
+ if (!this.createValues.description.trim()) {
293
+ this.createStepIndex = 1;
294
+ this.syncCreateInput();
295
+ this.createError = "Description is required.";
296
+ return;
297
+ }
298
+ this.mode = "generating";
299
+ const runId = ++this.generationRunId;
300
+ const abortController = new AbortController;
301
+ this.generationAbortController = abortController;
302
+ this.syncFocus();
303
+ this.requestRender();
304
+ const created = await this.options.onCreate({ name, description: this.createValues.description.trim(), allowedTools: [], location: this.createLocation }, abortController.signal);
305
+ if (this.generationRunId !== runId)
306
+ return;
307
+ this.generationAbortController = undefined;
308
+ if (abortController.signal.aborted || !created) {
309
+ this.mode = "create";
310
+ this.syncFocus();
311
+ this.requestRender();
312
+ return;
313
+ }
314
+ await this.refreshRegistry(created.path);
315
+ this.openPreview(this.registry.allSkills.find((skill) => skill.path === created.path) ?? created);
316
+ }
317
+ async refreshRegistry(preferredPath) {
318
+ this.registry = await this.options.onRefresh();
319
+ this.refreshBrowseList(preferredPath);
320
+ if (this.previewSkillPath) {
321
+ const current = this.registry.allSkills.find((skill) => skill.path === this.previewSkillPath);
322
+ if (!current) {
323
+ this.exitToBrowse(preferredPath);
324
+ return;
325
+ }
326
+ this.preview?.setSkill(current);
327
+ this.editorView?.setSkill(current);
328
+ }
329
+ }
330
+ async toggleSkill(skill) {
331
+ const nextEnabled = !skill.enabled;
332
+ try {
333
+ await this.options.onToggle(skill, nextEnabled);
334
+ await this.refreshRegistry(skill.path);
335
+ this.ctx.ui.notify(`${nextEnabled ? "Enabled" : "Disabled"} ${skill.name}. Run /reload to fully apply the change.`, "info");
336
+ } catch (error) {
337
+ this.ctx.ui.notify(error instanceof Error ? error.message : "Failed to update skill visibility", "error");
338
+ }
339
+ this.requestRender();
340
+ }
341
+ async confirmDelete() {
342
+ const skill = this.deleteSkillPath ? this.registry.allSkills.find((entry) => entry.path === this.deleteSkillPath) : undefined;
343
+ if (!skill) {
344
+ this.exitToBrowse();
345
+ return;
346
+ }
347
+ const deleted = await this.options.onDelete(skill);
348
+ if (!deleted) {
349
+ this.mode = this.deleteReturnMode === "preview" ? "preview" : "browse";
350
+ this.syncFocus();
351
+ this.requestRender();
352
+ return;
353
+ }
354
+ this.deleteSkillPath = undefined;
355
+ this.previewSkillPath = undefined;
356
+ this.preview = undefined;
357
+ await this.refreshRegistry();
358
+ this.exitToBrowse();
359
+ }
360
+ async submitRename(value) {
361
+ const skill = this.getCurrentSkill();
362
+ if (!skill) {
363
+ this.exitToBrowse();
364
+ return;
365
+ }
366
+ try {
367
+ const renamed = await renameSkillEntry(this.ctx, skill, value);
368
+ if (!renamed) {
369
+ this.closeRenameDialog();
370
+ return;
371
+ }
372
+ this.previewSkillPath = renamed.path;
373
+ await this.refreshRegistry(renamed.path);
374
+ this.closeRenameDialog();
375
+ } catch (error) {
376
+ this.renameError = error instanceof Error ? error.message : "Failed to rename skill";
377
+ this.requestRender();
378
+ }
379
+ }
380
+ async saveEditedSkill(raw) {
381
+ const skill = this.getCurrentSkill();
382
+ if (!skill) {
383
+ this.exitToBrowse();
384
+ return;
385
+ }
386
+ try {
387
+ const parsed = parseEditableSkillDocument(raw, skill.name);
388
+ writeFileSync(skill.path, parsed.raw, "utf8");
389
+ await this.refreshRegistry(skill.path);
390
+ this.preview?.setSkill(this.registry.allSkills.find((entry) => entry.path === skill.path) ?? toUpdatedSkill(skill, parsed));
391
+ this.ctx.ui.notify(`Updated skill: ${skill.name}`, "info");
392
+ this.closeEditor();
393
+ } catch (error) {
394
+ this.editorView?.setMessage(error instanceof Error ? error.message : "Failed to save skill", "error");
395
+ this.requestRender();
396
+ }
397
+ }
398
+ render(width) {
399
+ if (this.mode === "preview")
400
+ return this.preview?.render(width) ?? [];
401
+ if (this.mode === "edit")
402
+ return this.editorView?.render(width) ?? [];
403
+ if (this.mode === "rename")
404
+ return this.renderRenameDialog(width);
405
+ if (this.mode === "delete-confirm")
406
+ return this.renderDeleteDialog(width);
407
+ if (this.mode === "generating")
408
+ return this.renderGeneratingDialog(width);
409
+ return this.mode === "create" ? this.renderCreate(width) : this.renderBrowse(width);
410
+ }
411
+ renderBrowse(width) {
412
+ const innerWidth = Math.max(1, width - 4);
413
+ const root = new Container;
414
+ const enabledCount = this.registry.allSkills.filter((skill) => skill.enabled).length;
415
+ const totalCount = this.registry.allSkills.length;
416
+ root.addChild(new SearchInputLine(this.browseInput, this.theme));
417
+ root.addChild(new Spacer(1));
418
+ const list = new Container;
419
+ const entries = [{ kind: "create" }];
420
+ const own = this.filteredSkills.filter((skill) => isDeletableSkill(skill));
421
+ const library = this.filteredSkills.filter((skill) => !isDeletableSkill(skill));
422
+ if (own.length > 0)
423
+ entries.push({ kind: "header", label: "Your Skills" }, ...own.map((skill) => ({ kind: "skill", skill })));
424
+ if (library.length > 0)
425
+ entries.push({ kind: "header", label: "Library Skills" }, ...library.map((skill) => ({ kind: "skill", skill })));
426
+ let selectedDisplayIndex = 0;
427
+ let selectableIndex = 0;
428
+ for (let i = 0;i < entries.length; i++) {
429
+ const entry = entries[i];
430
+ if (entry.kind === "create" || entry.kind === "skill") {
431
+ if (selectableIndex === this.selectedIndex) {
432
+ selectedDisplayIndex = i;
433
+ break;
434
+ }
435
+ selectableIndex += 1;
436
+ }
437
+ }
438
+ const { startIndex, endIndex } = responsiveBrowseWindow(this.configuredListRows, this.tui.terminal.rows, entries.length, selectedDisplayIndex, this.popupMaxHeight);
439
+ selectableIndex = 0;
440
+ const ellipsis = this.theme.fg("dim", "...");
441
+ for (let i = 0;i < endIndex; i++) {
442
+ const entry = entries[i];
443
+ const isSelectable = entry.kind === "create" || entry.kind === "skill";
444
+ const isSelected = isSelectable && selectableIndex === this.selectedIndex;
445
+ if (i >= startIndex) {
446
+ if (entry.kind === "header") {
447
+ list.addChild(new Spacer(1));
448
+ list.addChild(new SingleLineText(skillSectionTitle(this.theme, entry.label), ellipsis));
449
+ } else if (entry.kind === "create") {
450
+ const prefix = " ";
451
+ const label = "Create new skill";
452
+ list.addChild(new ListLineText(`${prefix}${label}${this.theme.fg("dim", " — generate and save a new skill")}`, isSelected, this.theme, ellipsis));
453
+ } else {
454
+ const skill = entry.skill;
455
+ const prefix = " ";
456
+ const name = skill.enabled ? skill.name : this.theme.fg("muted", skill.name);
457
+ const status = skill.enabled ? "" : this.theme.fg("warning", " [disabled]");
458
+ const scope = this.theme.fg("muted", ` [${scopeLabel(skill)}]`);
459
+ const source = packageLabel(skill) ? this.theme.fg("muted", ` [${packageLabel(skill)}]`) : "";
460
+ const description = this.theme.fg("dim", ` — ${skill.description}`);
461
+ list.addChild(new ListLineText(`${prefix}${name}${status}${scope}${source}${description}`, isSelected, this.theme, ellipsis));
462
+ }
463
+ }
464
+ if (isSelectable)
465
+ selectableIndex += 1;
466
+ }
467
+ if (entries.length === 1 && this.filteredSkills.length === 0)
468
+ list.addChild(new Text(this.theme.fg("dim", "No skills match your search."), 1, 0));
469
+ root.addChild(list);
470
+ root.addChild(new Spacer(1));
471
+ const selected = this.getSelectedSkill();
472
+ const actions = [["-/=", "page"]];
473
+ if (selected) {
474
+ actions.push(["tab", "preview"], ["ctrl+x", "enable/disable"]);
475
+ if (!this.browseQuery && isDeletableSkill(selected))
476
+ actions.push(["backspace", "delete"]);
477
+ }
478
+ root.addChild(new Text(skillKeyHints(this.theme, actions), 1, 0));
479
+ return renderFrame(this.theme, width, root.render(innerWidth), undefined, "Skills Manager", `${enabledCount}/${totalCount} enabled`);
480
+ }
481
+ renderCreate(width) {
482
+ const innerWidth = Math.max(1, width - 4);
483
+ const step = this.currentCreateStep;
484
+ const root = new Container;
485
+ root.addChild(new Text(skillEntityTitle(this.theme, `${step.title} (${this.createStepIndex + 1}/${CREATE_STEPS.length})`), 1, 0));
486
+ root.addChild(new Spacer(1));
487
+ if (step.id === "name") {
488
+ root.addChild(this.browseInput);
489
+ root.addChild(new Spacer(1));
490
+ root.addChild(new Text(this.theme.fg("dim", step.hint), 1, 0));
491
+ } else if (step.id === "description") {
492
+ root.addChild(new PrefixedEditor(this.descriptionEditor));
493
+ root.addChild(new Spacer(1));
494
+ root.addChild(new Text(this.theme.fg("dim", step.hint), 1, 0));
495
+ } else if ("options" in step) {
496
+ for (const option of step.options) {
497
+ const selected = option.value === this.createLocation;
498
+ root.addChild(new ListLineText(` ${option.label}${this.theme.fg(selected ? "text" : "dim", ` — ${option.description}`)}`, selected, this.theme));
499
+ }
500
+ root.addChild(new Spacer(1));
501
+ root.addChild(new Text(this.theme.fg("dim", step.hint), 1, 0));
502
+ }
503
+ if (this.createError) {
504
+ root.addChild(new Spacer(1));
505
+ root.addChild(new Text(this.theme.fg("error", this.createError), 1, 0));
506
+ }
507
+ root.addChild(new Spacer(1));
508
+ const footer = step.id === "description" ? skillKeyHints(this.theme, [["alt+←", "back"], ["alt+→", "next"]]) : step.id === "location" ? skillKeyHints(this.theme, [["alt+←", "back"]]) : skillKeyHints(this.theme, [["alt+←", "back"], ["alt+→", "next"]]);
509
+ root.addChild(new Text(footer, 1, 0));
510
+ return renderFrame(this.theme, width, root.render(innerWidth));
511
+ }
512
+ renderRenameDialog(width) {
513
+ const lines = [skillEntityTitle(this.theme, "Rename skill"), "", this.theme.fg("dim", "enter new skill name (lowercase letters, numbers, hyphens)"), "", ...this.renameInput.render(Math.max(1, Math.min(width - 4, 64)))];
514
+ if (this.renameError)
515
+ lines.push("", toneText(this.theme, "error", this.renameError));
516
+ return renderCenteredDialog(this.theme, width, lines);
517
+ }
518
+ renderDeleteDialog(width) {
519
+ const skill = this.deleteSkillPath ? this.registry.allSkills.find((entry) => entry.path === this.deleteSkillPath) : undefined;
520
+ const innerWidth = Math.max(1, Math.min(width - 4, 64));
521
+ const message = skill ? `Delete ${skill.name}? This removes ${skillStorageTarget(skill)} and cannot be undone.` : "Delete this skill?";
522
+ return renderCenteredDialog(this.theme, width, [skillEntityTitle(this.theme, "Delete skill"), "", ...wrapTextWithAnsi(message, innerWidth), ""]);
523
+ }
524
+ renderGeneratingDialog(width) {
525
+ const modelLabel = this.ctx.model?.id ?? "fallback template";
526
+ return renderCenteredDialog(this.theme, width, [skillEntityTitle(this.theme, "Generating skill"), "", this.theme.fg("dim", `Using ${modelLabel} to draft SKILL.md.`), this.theme.fg("dim", "The preview opens when generation finishes.")]);
527
+ }
528
+ handleInput(data) {
529
+ if (this.mode === "generating") {
530
+ if (matchesKey(data, Key.escape)) {
531
+ this.generationAbortController?.abort();
532
+ this.generationAbortController = undefined;
533
+ this.generationRunId += 1;
534
+ this.mode = "create";
535
+ this.syncFocus();
536
+ this.requestRender();
537
+ }
538
+ return;
539
+ }
540
+ if (this.mode === "rename") {
541
+ if (matchesKey(data, Key.escape)) {
542
+ this.closeRenameDialog();
543
+ return;
544
+ }
545
+ if (this.renameError)
546
+ this.renameError = undefined;
547
+ this.renameInput.handleInput(data);
548
+ return;
549
+ }
550
+ if (this.mode === "delete-confirm") {
551
+ if (matchesKey(data, Key.escape)) {
552
+ this.mode = this.deleteReturnMode === "preview" ? "preview" : "browse";
553
+ this.syncFocus();
554
+ return;
555
+ }
556
+ if (matchesKey(data, Key.enter))
557
+ this.confirmDelete();
558
+ return;
559
+ }
560
+ if (this.mode === "edit") {
561
+ this.editorView?.handleInput(data);
562
+ return;
563
+ }
564
+ if (this.mode === "preview") {
565
+ const skill = this.getCurrentSkill();
566
+ if (!skill) {
567
+ this.exitToBrowse();
568
+ return;
569
+ }
570
+ if (matchesKey(data, Key.escape) || matchesKey(data, Key.tab)) {
571
+ this.exitToBrowse(skill.path);
572
+ return;
573
+ }
574
+ if (matchesKey(data, Key.enter)) {
575
+ if (!skill.enabled)
576
+ this.ctx.ui.notify("Enable this skill first with ctrl+x", "info");
577
+ else
578
+ this.done(skill);
579
+ return;
580
+ }
581
+ if (matchesKey(data, Key.alt("x")) || matchesKey(data, Key.ctrl("x"))) {
582
+ this.toggleSkill(skill);
583
+ return;
584
+ }
585
+ if (isDeletableSkill(skill) && (matchesKey(data, Key.alt("e")) || matchesKey(data, Key.ctrl("e")))) {
586
+ this.openEditor();
587
+ return;
588
+ }
589
+ if (isDeletableSkill(skill) && (matchesKey(data, Key.alt("r")) || matchesKey(data, Key.ctrl("r")))) {
590
+ this.openRenameDialog();
591
+ return;
592
+ }
593
+ if (isDeletableSkill(skill) && (matchesKey(data, Key.backspace) || matchesKey(data, "delete"))) {
594
+ this.openDeleteConfirm(skill, "preview");
595
+ return;
596
+ }
597
+ this.preview?.handleInput(data);
598
+ return;
599
+ }
600
+ if (this.mode === "create") {
601
+ this.handleCreateInput(data);
602
+ return;
603
+ }
604
+ this.handleBrowseInput(data);
605
+ }
606
+ handleBrowseInput(data) {
607
+ if (matchesKey(data, Key.up)) {
608
+ this.selectedIndex = this.selectedIndex === 0 ? this.filteredSkills.length : this.selectedIndex - 1;
609
+ return;
610
+ }
611
+ if (matchesKey(data, Key.down)) {
612
+ this.selectedIndex = this.selectedIndex === this.filteredSkills.length ? 0 : this.selectedIndex + 1;
613
+ return;
614
+ }
615
+ if (matchesKey(data, "-") || matchesKey(data, Key.pageUp)) {
616
+ this.selectedIndex = responsiveBrowsePageSelection(this.configuredListRows, this.tui.terminal.rows, this.selectedIndex, this.filteredSkills.length, -1, this.popupMaxHeight);
617
+ return;
618
+ }
619
+ if (matchesKey(data, "=") || matchesKey(data, Key.pageDown)) {
620
+ this.selectedIndex = responsiveBrowsePageSelection(this.configuredListRows, this.tui.terminal.rows, this.selectedIndex, this.filteredSkills.length, 1, this.popupMaxHeight);
621
+ return;
622
+ }
623
+ if (matchesKey(data, Key.enter)) {
624
+ if (this.selectedIndex === 0) {
625
+ this.enterCreateMode();
626
+ return;
627
+ }
628
+ const skill = this.getSelectedSkill();
629
+ if (!skill)
630
+ return;
631
+ if (!skill.enabled)
632
+ this.ctx.ui.notify("Enable this skill first with ctrl+x", "info");
633
+ else
634
+ this.done(skill);
635
+ return;
636
+ }
637
+ if (matchesKey(data, Key.tab)) {
638
+ const skill = this.getSelectedSkill();
639
+ if (skill)
640
+ this.openPreview(skill);
641
+ return;
642
+ }
643
+ if (matchesKey(data, Key.alt("x")) || matchesKey(data, Key.ctrl("x"))) {
644
+ const skill = this.getSelectedSkill();
645
+ if (skill)
646
+ this.toggleSkill(skill);
647
+ return;
648
+ }
649
+ if (matchesKey(data, Key.backspace) && !this.browseInput.getValue()) {
650
+ const skill = this.getSelectedSkill();
651
+ if (skill && isDeletableSkill(skill))
652
+ this.openDeleteConfirm(skill, "browse");
653
+ return;
654
+ }
655
+ if (matchesKey(data, Key.escape)) {
656
+ if (this.browseInput.getValue()) {
657
+ this.browseQuery = "";
658
+ this.browseInput.setValue("");
659
+ this.refreshBrowseList();
660
+ } else
661
+ this.done(null);
662
+ return;
663
+ }
664
+ this.browseInput.handleInput(data);
665
+ this.browseQuery = this.browseInput.getValue();
666
+ this.refreshBrowseList();
667
+ }
668
+ handleCreateInput(data) {
669
+ if (matchesKey(data, Key.escape)) {
670
+ this.exitToBrowse();
671
+ return;
672
+ }
673
+ if (matchesKey(data, Key.alt("left"))) {
674
+ this.goToPreviousCreateStep();
675
+ return;
676
+ }
677
+ if (matchesKey(data, Key.alt("right"))) {
678
+ this.advanceCreate();
679
+ return;
680
+ }
681
+ if (matchesKey(data, Key.enter) && this.currentCreateStep.id !== "description") {
682
+ this.advanceCreate();
683
+ return;
684
+ }
685
+ this.createError = undefined;
686
+ const step = this.currentCreateStep;
687
+ if (step.id === "name") {
688
+ this.browseInput.handleInput(data);
689
+ this.createValues.name = this.browseInput.getValue();
690
+ return;
691
+ }
692
+ if (step.id === "location") {
693
+ if (matchesKey(data, Key.up))
694
+ this.createLocation = this.createLocation === "project" ? "global" : "project";
695
+ else if (matchesKey(data, Key.down))
696
+ this.createLocation = this.createLocation === "project" ? "global" : "project";
697
+ return;
698
+ }
699
+ this.descriptionEditor.handleInput(data);
700
+ if (!matchesKey(data, Key.enter))
701
+ this.createValues.description = this.descriptionEditor.getText();
702
+ }
703
+ }
704
+ export async function showSkillsManager(ctx, registry, options) {
705
+ const releaseModalLock = acquirekendexModalLock();
706
+ try {
707
+ return await ctx.ui.custom((tui, theme, _kb, done) => {
708
+ const dialog = new SkillsManagerDialog(ctx, registry, theme, tui, done, options, () => tui.requestRender());
709
+ return {
710
+ get focused() {
711
+ return dialog.focused;
712
+ },
713
+ set focused(value) {
714
+ dialog.focused = value;
715
+ },
716
+ render(width) {
717
+ return dialog.render(width);
718
+ },
719
+ invalidate() {
720
+ dialog.invalidate();
721
+ },
722
+ handleInput(data) {
723
+ dialog.handleInput(data);
724
+ tui.requestRender();
725
+ }
726
+ };
727
+ }, {
728
+ overlay: true,
729
+ overlayOptions: {
730
+ anchor: "center",
731
+ width: settingOverlaySize("popupWidth", DEFAULT_POPUP_WIDTH, ctx.cwd),
732
+ maxHeight: sanitizePopupMaxHeight(settingOverlaySize("popupMaxHeight", DEFAULT_POPUP_MAX_HEIGHT, ctx.cwd))
733
+ }
734
+ });
735
+ } finally {
736
+ releaseModalLock();
737
+ }
738
+ }