@duckmind/dm-windows-x64 0.63.4 → 0.63.9

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 (121) hide show
  1. package/dm.exe +0 -0
  2. package/extensions/.dm-extensions.json +236 -13
  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-mcp-adapter/OAUTH.md +3 -0
  15. package/extensions/dm-mcp-adapter/THIRD_PARTY_NOTICES.md +4 -0
  16. package/extensions/dm-mcp-adapter/abort.js +35 -0
  17. package/extensions/dm-mcp-adapter/agent-dir.js +45 -0
  18. package/extensions/dm-mcp-adapter/agent-plugin-loader.js +349 -0
  19. package/extensions/dm-mcp-adapter/app-bridge.bundle.js +5843 -0
  20. package/extensions/dm-mcp-adapter/cli.js +354 -0
  21. package/extensions/dm-mcp-adapter/commands.js +619 -0
  22. package/extensions/dm-mcp-adapter/config.js +1060 -0
  23. package/extensions/dm-mcp-adapter/consent-manager.js +60 -0
  24. package/extensions/dm-mcp-adapter/direct-tools.js +544 -0
  25. package/extensions/dm-mcp-adapter/dist/agent-dir.js +45 -0
  26. package/extensions/dm-mcp-adapter/dist/agent-plugin-loader.js +349 -0
  27. package/extensions/dm-mcp-adapter/dist/config.js +1060 -0
  28. package/extensions/dm-mcp-adapter/dist/mcp-bearer-store.js +311 -0
  29. package/extensions/dm-mcp-adapter/dist/metadata-cache.js +285 -0
  30. package/extensions/dm-mcp-adapter/dist/package-mcp-loader.js +144 -0
  31. package/extensions/dm-mcp-adapter/dist/resource-tools.js +7 -0
  32. package/extensions/dm-mcp-adapter/dist/types.js +247 -0
  33. package/extensions/dm-mcp-adapter/dist/ui-app-bridge-helpers.js +36 -0
  34. package/extensions/dm-mcp-adapter/dist/ui-stream-types.js +58 -0
  35. package/extensions/dm-mcp-adapter/dist/ui-tool-visibility.js +26 -0
  36. package/extensions/dm-mcp-adapter/dist/utils.js +307 -0
  37. package/extensions/dm-mcp-adapter/elicitation-handler.js +306 -0
  38. package/extensions/dm-mcp-adapter/error-signal.js +16 -0
  39. package/extensions/dm-mcp-adapter/errors.js +225 -0
  40. package/extensions/dm-mcp-adapter/failure-backoff.js +19 -0
  41. package/extensions/dm-mcp-adapter/glimpse-ui.js +61 -0
  42. package/extensions/dm-mcp-adapter/host-html-template.js +464 -0
  43. package/extensions/dm-mcp-adapter/index.js +1079 -0
  44. package/extensions/dm-mcp-adapter/init.js +624 -0
  45. package/extensions/dm-mcp-adapter/json-schema-validator.js +53 -0
  46. package/extensions/dm-mcp-adapter/lifecycle.js +385 -0
  47. package/extensions/dm-mcp-adapter/logger.js +115 -0
  48. package/extensions/dm-mcp-adapter/mcp-auth-flow.js +824 -0
  49. package/extensions/dm-mcp-adapter/mcp-auth.js +732 -0
  50. package/extensions/dm-mcp-adapter/mcp-bearer-store.js +311 -0
  51. package/extensions/dm-mcp-adapter/mcp-callback-server.js +388 -0
  52. package/extensions/dm-mcp-adapter/mcp-code.js +291 -0
  53. package/extensions/dm-mcp-adapter/mcp-keyring-helper.cjs +88 -0
  54. package/extensions/dm-mcp-adapter/mcp-oauth-provider.js +490 -0
  55. package/extensions/dm-mcp-adapter/mcp-output-guard.js +386 -0
  56. package/extensions/dm-mcp-adapter/mcp-panel.js +951 -0
  57. package/extensions/dm-mcp-adapter/mcp-probe.js +151 -0
  58. package/extensions/dm-mcp-adapter/mcp-references.js +263 -0
  59. package/extensions/dm-mcp-adapter/mcp-script-worker.mjs +126 -0
  60. package/extensions/dm-mcp-adapter/mcp-setup-panel.js +578 -0
  61. package/extensions/dm-mcp-adapter/mcp-status.js +80 -0
  62. package/extensions/dm-mcp-adapter/mcp-trace.js +215 -0
  63. package/extensions/dm-mcp-adapter/metadata-cache.js +292 -0
  64. package/extensions/dm-mcp-adapter/namespace-tools.js +155 -0
  65. package/extensions/dm-mcp-adapter/npx-resolver.js +465 -0
  66. package/extensions/dm-mcp-adapter/oauth-handler.js +17 -0
  67. package/extensions/dm-mcp-adapter/oauth.js +17 -0
  68. package/extensions/dm-mcp-adapter/onboarding-state.js +62 -0
  69. package/extensions/dm-mcp-adapter/package-mcp-loader.js +144 -0
  70. package/extensions/dm-mcp-adapter/package.json +68 -0
  71. package/extensions/dm-mcp-adapter/panel-keys.js +26 -0
  72. package/extensions/dm-mcp-adapter/prompts.js +258 -0
  73. package/extensions/dm-mcp-adapter/proxy-modes.js +1336 -0
  74. package/extensions/dm-mcp-adapter/request-headers-command.js +293 -0
  75. package/extensions/dm-mcp-adapter/resource-tools.js +7 -0
  76. package/extensions/dm-mcp-adapter/runtime-owner.js +79 -0
  77. package/extensions/dm-mcp-adapter/sampling-handler.js +222 -0
  78. package/extensions/dm-mcp-adapter/sandbox-proxy-template.js +190 -0
  79. package/extensions/dm-mcp-adapter/search-ranking.js +198 -0
  80. package/extensions/dm-mcp-adapter/server-manager.js +1258 -0
  81. package/extensions/dm-mcp-adapter/session-recovery.js +82 -0
  82. package/extensions/dm-mcp-adapter/skills/mcp-scripting/SKILL.md +39 -0
  83. package/extensions/dm-mcp-adapter/state.js +0 -0
  84. package/extensions/dm-mcp-adapter/test-runner.mjs +18 -0
  85. package/extensions/dm-mcp-adapter/tool-approval.js +151 -0
  86. package/extensions/dm-mcp-adapter/tool-metadata.js +247 -0
  87. package/extensions/dm-mcp-adapter/tool-registrar.js +212 -0
  88. package/extensions/dm-mcp-adapter/tool-result-renderer.js +355 -0
  89. package/extensions/dm-mcp-adapter/ts-shape.js +157 -0
  90. package/extensions/dm-mcp-adapter/types.js +264 -0
  91. package/extensions/dm-mcp-adapter/ui-app-bridge-helpers.js +36 -0
  92. package/extensions/dm-mcp-adapter/ui-resource-handler.js +209 -0
  93. package/extensions/dm-mcp-adapter/ui-server.js +815 -0
  94. package/extensions/dm-mcp-adapter/ui-session.js +455 -0
  95. package/extensions/dm-mcp-adapter/ui-stream-types.js +58 -0
  96. package/extensions/dm-mcp-adapter/ui-tool-visibility.js +26 -0
  97. package/extensions/dm-mcp-adapter/unix-socket-transport.js +83 -0
  98. package/extensions/dm-mcp-adapter/utils.js +307 -0
  99. package/extensions/dm-skills-manager/THIRD_PARTY_NOTICES.md +27 -0
  100. package/extensions/dm-skills-manager/extensions/skills-manager/components.js +265 -0
  101. package/extensions/dm-skills-manager/extensions/skills-manager/constants.js +31 -0
  102. package/extensions/dm-skills-manager/extensions/skills-manager/creation-fallback.js +20 -0
  103. package/extensions/dm-skills-manager/extensions/skills-manager/creation.js +145 -0
  104. package/extensions/dm-skills-manager/extensions/skills-manager/dialog.js +738 -0
  105. package/extensions/dm-skills-manager/extensions/skills-manager/dm-ai-compat.js +15 -0
  106. package/extensions/dm-skills-manager/extensions/skills-manager/format.js +125 -0
  107. package/extensions/dm-skills-manager/extensions/skills-manager/glyphs.js +142 -0
  108. package/extensions/dm-skills-manager/extensions/skills-manager/layout.js +93 -0
  109. package/extensions/dm-skills-manager/extensions/skills-manager/paths.js +76 -0
  110. package/extensions/dm-skills-manager/extensions/skills-manager/registry.js +95 -0
  111. package/extensions/dm-skills-manager/extensions/skills-manager/settings.js +93 -0
  112. package/extensions/dm-skills-manager/extensions/skills-manager/startup.js +32 -0
  113. package/extensions/dm-skills-manager/extensions/skills-manager/toggle.js +54 -0
  114. package/extensions/dm-skills-manager/extensions/skills-manager/types.js +14 -0
  115. package/extensions/dm-skills-manager/extensions/skills-manager/ui.js +121 -0
  116. package/extensions/dm-skills-manager/extensions/skills-manager.js +111 -0
  117. package/extensions/dm-skills-manager/package.json +121 -0
  118. package/package.json +13 -1
  119. package/extensions/dm-localllm-provider/detect.test.js +0 -1051
  120. package/extensions/dm-localllm-provider/index.test.js +0 -149
  121. package/extensions/dm-localllm-provider/keychain.test.js +0 -61
@@ -0,0 +1,307 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { homedir, platform } from "node:os";
3
+ import { extname, isAbsolute, join } from "node:path";
4
+ async function execOpen(pi, target, browser, signal) {
5
+ const os = platform();
6
+ if (os === "darwin") {
7
+ if (browser) {
8
+ return isAbsolute(browser) && extname(browser).toLowerCase() !== ".app" ? pi.exec(browser, [target], signal ? { signal } : {}) : pi.exec("open", ["-a", browser, target], signal ? { signal } : {});
9
+ }
10
+ return pi.exec("open", [target], signal ? { signal } : {});
11
+ }
12
+ if (os === "win32") {
13
+ return browser ? pi.exec("cmd", ["/c", "start", "", browser, target], signal ? { signal } : {}) : pi.exec("cmd", ["/c", "start", "", target], signal ? { signal } : {});
14
+ }
15
+ return browser ? pi.exec(browser, [target], signal ? { signal } : {}) : pi.exec("xdg-open", [target], signal ? { signal } : {});
16
+ }
17
+ export async function openUrl(pi, url, browser, signal) {
18
+ const result = await execOpen(pi, url, browser, signal);
19
+ if (result.code !== 0) {
20
+ throw new Error(result.stderr || `Failed to open browser (exit code ${result.code})`);
21
+ }
22
+ }
23
+ export async function openPath(pi, targetPath) {
24
+ const result = await execOpen(pi, targetPath);
25
+ if (result.code !== 0) {
26
+ throw new Error(result.stderr || `Failed to open path (exit code ${result.code})`);
27
+ }
28
+ }
29
+ export async function parallelLimit(items, limit, fn) {
30
+ const results = [];
31
+ const iterator = items.entries();
32
+ async function worker() {
33
+ while (true) {
34
+ const next = iterator.next();
35
+ if (next.done)
36
+ return;
37
+ const [index, item] = next.value;
38
+ results[index] = await fn(item);
39
+ }
40
+ }
41
+ const workers = Array(Math.min(limit, items.length)).fill(null).map(() => worker());
42
+ await Promise.all(workers);
43
+ return results;
44
+ }
45
+ export function getConfigPathFromArgv() {
46
+ const idx = process.argv.indexOf("--mcp-config");
47
+ if (idx >= 0 && idx + 1 < process.argv.length) {
48
+ return process.argv[idx + 1];
49
+ }
50
+ return;
51
+ }
52
+ export function interpolateEnvVars(value, environment = process.env) {
53
+ return value.replace(/\$\{(\w+)\}/g, (_, name) => environment[name] ?? "").replace(/\$env:(\w+)/g, (_, name) => environment[name] ?? "").replace(/\{env:(\w+)\}/g, (_, name) => environment[name] ?? "");
54
+ }
55
+ function getMissingEnvVars(value, environment) {
56
+ const missing = new Set;
57
+ for (const match of value.matchAll(/\$\{(\w+)\}|\$env:(\w+)|\{env:(\w+)\}/g)) {
58
+ const name = match[1] ?? match[2] ?? match[3];
59
+ if (name && environment[name] === undefined) {
60
+ missing.add(name);
61
+ }
62
+ }
63
+ return [...missing];
64
+ }
65
+ export function toStringRecord(value) {
66
+ if (!value || typeof value !== "object" || Array.isArray(value))
67
+ return;
68
+ const result = {};
69
+ for (const [key, entry] of Object.entries(value)) {
70
+ if (typeof entry === "string")
71
+ result[key] = entry;
72
+ }
73
+ return Object.keys(result).length > 0 ? result : undefined;
74
+ }
75
+ function interpolateSecretExpression(value, environment) {
76
+ if (value.startsWith("!!"))
77
+ return interpolateEnvVars(value.slice(1), environment);
78
+ return value.startsWith("!") ? value : interpolateEnvVars(value, environment);
79
+ }
80
+ export function interpolateEnvRecord(values, environment = process.env) {
81
+ if (!values)
82
+ return;
83
+ return Object.fromEntries(Object.entries(values).map(([key, value]) => [
84
+ key,
85
+ interpolateSecretExpression(value, environment)
86
+ ]));
87
+ }
88
+ const COMMAND_SECRET_TIMEOUT_MS = 1e4;
89
+ const COMMAND_SECRET_MAX_OUTPUT_BYTES = 1024 * 1024;
90
+ export function resolveCommandSecret(value, context) {
91
+ if (value === undefined)
92
+ return;
93
+ if (value.startsWith("!!"))
94
+ return interpolateEnvVars(value.slice(1));
95
+ if (!value.startsWith("!"))
96
+ return interpolateEnvVars(value);
97
+ const result = spawnSync(value.slice(1), {
98
+ shell: true,
99
+ encoding: "utf8",
100
+ timeout: COMMAND_SECRET_TIMEOUT_MS,
101
+ maxBuffer: COMMAND_SECRET_MAX_OUTPUT_BYTES,
102
+ stdio: ["ignore", "pipe", "ignore"],
103
+ windowsHide: true
104
+ });
105
+ if (result.error) {
106
+ const code = result.error.code;
107
+ const reason = code === "ETIMEDOUT" ? "command timed out after 10 seconds" : code === "ENOBUFS" ? "command output exceeded 1 MiB" : "command failed to start";
108
+ throw new Error(`Failed to resolve ${context}: ${reason}`);
109
+ }
110
+ if (result.status !== 0) {
111
+ throw new Error(`Failed to resolve ${context}: command exited with code ${result.status ?? "unknown"}`);
112
+ }
113
+ const resolved = result.stdout.trim();
114
+ if (!resolved)
115
+ throw new Error(`Failed to resolve ${context}: command returned empty output`);
116
+ return resolved;
117
+ }
118
+ export function resolveCommandSecretsRecord(values, context) {
119
+ if (!values)
120
+ return;
121
+ return Object.fromEntries(Object.entries(values).map(([key, value]) => [
122
+ key,
123
+ resolveCommandSecret(value, context(key))
124
+ ]));
125
+ }
126
+ export function resolveServerUrl(definition, environment = process.env) {
127
+ if (definition.url == null)
128
+ return;
129
+ if (typeof definition.url !== "string") {
130
+ throw new Error("MCP server URL must be a string");
131
+ }
132
+ const missing = getMissingEnvVars(definition.url, environment);
133
+ if (missing.length > 0) {
134
+ throw new Error(`Missing environment variable${missing.length === 1 ? "" : "s"} in MCP server URL: ${missing.join(", ")}`);
135
+ }
136
+ const resolved = interpolateEnvVars(definition.url, environment);
137
+ try {
138
+ new URL(resolved);
139
+ } catch (error) {
140
+ throw new Error(`Invalid MCP server URL after environment interpolation: ${resolved}`, { cause: error });
141
+ }
142
+ return resolved;
143
+ }
144
+ export function resolveConfigPath(value, environment = process.env) {
145
+ if (value === undefined)
146
+ return;
147
+ const resolved = interpolateEnvVars(value, environment);
148
+ if (resolved === "~")
149
+ return homedir();
150
+ if (resolved.startsWith("~/") || resolved.startsWith("~\\")) {
151
+ return join(homedir(), resolved.slice(2));
152
+ }
153
+ return resolved;
154
+ }
155
+ export function resolveBearerToken(definition, environment = process.env) {
156
+ if (definition.bearerToken !== undefined) {
157
+ return interpolateSecretExpression(definition.bearerToken, environment);
158
+ }
159
+ return definition.bearerTokenEnv ? environment[definition.bearerTokenEnv] : undefined;
160
+ }
161
+ export function stripOscSequences(text) {
162
+ let result = "";
163
+ let index = 0;
164
+ while (index < text.length) {
165
+ const isEscOsc = text.charCodeAt(index) === 27 && text[index + 1] === "]";
166
+ const isC1Osc = text.charCodeAt(index) === 157;
167
+ if (!isEscOsc && !isC1Osc) {
168
+ result += text[index++];
169
+ continue;
170
+ }
171
+ index += isEscOsc ? 2 : 1;
172
+ while (index < text.length) {
173
+ const code = text.charCodeAt(index++);
174
+ if (code === 7 || code === 156)
175
+ break;
176
+ if (code === 27 && text[index] === "\\") {
177
+ index++;
178
+ break;
179
+ }
180
+ }
181
+ }
182
+ return result;
183
+ }
184
+ export function sanitizeTerminalText(text) {
185
+ return stripOscSequences(text).replace(/(?:\x1b\[[0-?]*[ -/]*[@-~]|\x1b[@-Z\\-_])/g, "").replace(/[\u0000-\u001f\u007f-\u009f]+/g, " ").replace(/\s+/g, " ").trim();
186
+ }
187
+ export function formatTerminalError(error) {
188
+ const messages = [];
189
+ const seen = new Set;
190
+ const collect = (value) => {
191
+ if (seen.has(value))
192
+ return;
193
+ if (typeof value === "object" && value !== null || typeof value === "function")
194
+ seen.add(value);
195
+ if (value instanceof AggregateError) {
196
+ const countBefore = messages.length;
197
+ for (const nested of value.errors)
198
+ collect(nested);
199
+ if (value.cause !== undefined)
200
+ collect(value.cause);
201
+ if (messages.length === countBefore && value.message)
202
+ messages.push(value.message);
203
+ return;
204
+ }
205
+ if (value instanceof Error) {
206
+ if (value.message)
207
+ messages.push(value.message);
208
+ if (value.cause !== undefined)
209
+ collect(value.cause);
210
+ return;
211
+ }
212
+ messages.push(String(value));
213
+ };
214
+ collect(error);
215
+ return sanitizeTerminalText([...new Set(messages)].join(": "));
216
+ }
217
+ export function truncateAtWord(text, target) {
218
+ if (!text || text.length <= target)
219
+ return text;
220
+ const truncated = text.slice(0, target);
221
+ const lastSpace = truncated.lastIndexOf(" ");
222
+ if (lastSpace > target * 0.6) {
223
+ return truncated.slice(0, lastSpace) + "...";
224
+ }
225
+ return truncated + "...";
226
+ }
227
+ export function normalizeDirectToolInputSchema(schema) {
228
+ const inputSchema = schema && typeof schema === "object" && !Array.isArray(schema) ? schema : { type: "object", properties: {} };
229
+ const { $schema, additionalProperties, ...normalized } = inputSchema;
230
+ return normalized;
231
+ }
232
+ export function normalizeToolArguments(value, context = "tool arguments") {
233
+ if (value === undefined || value === null || value === "")
234
+ return {};
235
+ if (typeof value === "string") {
236
+ const trimmed = value.trim();
237
+ if (trimmed === "")
238
+ return {};
239
+ let parsed;
240
+ try {
241
+ parsed = JSON.parse(trimmed);
242
+ } catch (error) {
243
+ throw new Error(`${context}: invalid args JSON (${error instanceof SyntaxError ? error.message : String(error)}); ` + `pass args as a JSON object, or as a valid JSON string encoding one`, { cause: error });
244
+ }
245
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
246
+ throw new Error(`${context}: expected a JSON object, got ${Array.isArray(parsed) ? "array" : typeof parsed}`);
247
+ }
248
+ return parsed;
249
+ }
250
+ if (typeof value !== "object" || Array.isArray(value)) {
251
+ throw new Error(`${context}: expected a JSON object, got ${Array.isArray(value) ? "array" : typeof value}`);
252
+ }
253
+ assertJsonSerializable(value, context);
254
+ try {
255
+ return JSON.parse(JSON.stringify(value));
256
+ } catch (error) {
257
+ throw new Error(`${context}: value is not JSON-serializable: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
258
+ }
259
+ }
260
+ function assertJsonSerializable(value, context, path = "") {
261
+ if (value === null)
262
+ return;
263
+ if (typeof value === "string" || typeof value === "boolean")
264
+ return;
265
+ if (typeof value === "number") {
266
+ if (!Number.isFinite(value))
267
+ throw new Error(`${context}: value at ${path || "root"} is not a finite number`);
268
+ return;
269
+ }
270
+ if (Array.isArray(value)) {
271
+ for (const [index, item] of value.entries())
272
+ assertJsonSerializable(item, context, `${path}[${index}]`);
273
+ return;
274
+ }
275
+ if (typeof value === "object") {
276
+ if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) {
277
+ throw new Error(`${context}: value at ${path || "root"} is not a plain JSON object`);
278
+ }
279
+ if (Object.getOwnPropertySymbols(value).length > 0) {
280
+ throw new Error(`${context}: value at ${path || "root"} has symbol keys`);
281
+ }
282
+ for (const [key, item] of Object.entries(value)) {
283
+ assertJsonSerializable(item, context, path ? `${path}.${key}` : key);
284
+ }
285
+ return;
286
+ }
287
+ throw new Error(`${context}: value at ${path || "root"} is not JSON-serializable`);
288
+ }
289
+ export function formatAuthRequiredMessage(config, serverName, defaultMessage) {
290
+ const template = config.settings?.authRequiredMessage;
291
+ return template ? template.replaceAll("${server}", serverName) : defaultMessage;
292
+ }
293
+ export function formatMcpStatus(config, message) {
294
+ if (config.settings?.mcpFooterStatus === "off")
295
+ return;
296
+ return `${config.settings?.showStatusIcon === false ? "MCP: " : "\uD83D\uDD0C MCP: "}${message}`;
297
+ }
298
+ export function extractToolUiStreamMode(toolMeta) {
299
+ const uiMeta = toolMeta?.ui;
300
+ if (!uiMeta || typeof uiMeta !== "object")
301
+ return;
302
+ const streamMode = uiMeta["dm-mcp-adapter.streamMode"];
303
+ if (streamMode === "eager" || streamMode === "stream-first") {
304
+ return streamMode;
305
+ }
306
+ return;
307
+ }
@@ -0,0 +1,27 @@
1
+ # Third-party notices
2
+
3
+ This package is based on ideas and portions of [`@kmiyh/pi-skills-menu`](https://github.com/Kmiyh/pi-skills-menu), which is licensed under the MIT License.
4
+
5
+ ```text
6
+ MIT License
7
+
8
+ Copyright (c) 2025 Kmiyh
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+ ```
@@ -0,0 +1,265 @@
1
+ import {
2
+ Container,
3
+ Editor,
4
+ Key,
5
+ Markdown,
6
+ matchesKey,
7
+ Spacer,
8
+ Text,
9
+ truncateToWidth,
10
+ visibleWidth
11
+ } from "@duckmind/dm-tui";
12
+ import { getMarkdownTheme } from "@duckmind/dm-coding-agent";
13
+ import { buildFrontmatterBlock } from "./format.js";
14
+ import { glyphs } from "./glyphs.js";
15
+ import { isDeletableSkill } from "./registry.js";
16
+ import {
17
+ getEditorTheme,
18
+ inlineLine,
19
+ packageLabel,
20
+ padAnsi,
21
+ scopeLabel,
22
+ skillEntityTitle,
23
+ skillKeyHints,
24
+ skillLocation,
25
+ skillSelectedLine,
26
+ toneText,
27
+ renderFrame
28
+ } from "./ui.js";
29
+
30
+ export class SingleLineText {
31
+ text;
32
+ ellipsis;
33
+ constructor(text, ellipsis = "...") {
34
+ this.text = text;
35
+ this.ellipsis = ellipsis;
36
+ }
37
+ render(width) {
38
+ return [truncateToWidth(inlineLine(this.text), width, this.ellipsis)];
39
+ }
40
+ invalidate() {}
41
+ }
42
+
43
+ export class ListLineText {
44
+ text;
45
+ selected;
46
+ theme;
47
+ ellipsis;
48
+ constructor(text, selected, theme, ellipsis = "...") {
49
+ this.text = text;
50
+ this.selected = selected;
51
+ this.theme = theme;
52
+ this.ellipsis = ellipsis;
53
+ }
54
+ render(width) {
55
+ const line = truncateToWidth(inlineLine(this.text), width, this.ellipsis);
56
+ return [this.selected ? skillSelectedLine(this.theme, line, width) : line];
57
+ }
58
+ invalidate() {}
59
+ }
60
+
61
+ export class PrefixedEditor {
62
+ editor;
63
+ prefix;
64
+ constructor(editor, prefix = "> ") {
65
+ this.editor = editor;
66
+ this.prefix = prefix;
67
+ }
68
+ render(width) {
69
+ const rendered = this.editor.render(Math.max(1, width - this.prefix.length));
70
+ const lines = rendered.length >= 2 ? rendered.slice(1, -1) : rendered;
71
+ return lines.length === 0 ? [this.prefix] : lines.map((line, index) => `${index === 0 ? this.prefix : " "}${line}`);
72
+ }
73
+ invalidate() {
74
+ this.editor.invalidate();
75
+ }
76
+ }
77
+
78
+ export class SearchInputLine {
79
+ input;
80
+ theme;
81
+ prefix;
82
+ constructor(input, theme, prefix = " ") {
83
+ this.input = input;
84
+ this.theme = theme;
85
+ this.prefix = prefix;
86
+ }
87
+ render(width) {
88
+ const inputWidth = Math.max(1, width - visibleWidth(this.prefix));
89
+ const line = truncateToWidth(`${this.prefix}${this.input.render(inputWidth)[0] ?? ""}`, width, "");
90
+ return [this.theme.bg("toolPendingBg", padAnsi(line, width))];
91
+ }
92
+ invalidate() {
93
+ this.input.invalidate();
94
+ }
95
+ }
96
+
97
+ export class ScrollableSkillPreview {
98
+ scrollOffset = 0;
99
+ lastInnerWidth = 1;
100
+ lastContentLines = [];
101
+ skill;
102
+ theme;
103
+ getTerminalRows;
104
+ constructor(skill, theme, getTerminalRows) {
105
+ this.skill = skill;
106
+ this.theme = theme;
107
+ this.getTerminalRows = getTerminalRows;
108
+ }
109
+ setSkill(skill) {
110
+ this.skill = skill;
111
+ this.scrollOffset = 0;
112
+ this.lastContentLines = [];
113
+ }
114
+ invalidate() {
115
+ this.lastContentLines = [];
116
+ }
117
+ maxHeight() {
118
+ return Math.max(10, Math.floor(this.getTerminalRows() * 0.78));
119
+ }
120
+ buildContentLines(innerWidth) {
121
+ const content = new Container;
122
+ const status = this.skill.enabled ? this.theme.fg("success", "enabled") : this.theme.fg("warning", "disabled");
123
+ const source = packageLabel(this.skill) ? `${packageLabel(this.skill)}` : this.skill.path;
124
+ content.addChild(new Text(skillEntityTitle(this.theme, this.skill.name), 0, 0));
125
+ content.addChild(new Text(`${this.theme.fg("muted", scopeLabel(this.skill))}${this.theme.fg("dim", ` ${glyphs().bullet.trim()} `)}${this.theme.fg("muted", source)}${this.theme.fg("dim", ` ${glyphs().bullet.trim()} `)}${status}`, 0, 0));
126
+ content.addChild(new Spacer(1));
127
+ content.addChild(new Text(this.theme.fg("muted", this.theme.bold("Description")), 0, 0));
128
+ content.addChild(new Text(this.skill.description, 0, 0));
129
+ content.addChild(new Spacer(1));
130
+ content.addChild(new Text(this.theme.fg("muted", this.theme.bold("Metadata")), 0, 0));
131
+ content.addChild(new Text(this.theme.fg("dim", buildFrontmatterBlock(this.skill)), 0, 0));
132
+ content.addChild(new Spacer(1));
133
+ content.addChild(new Text(this.theme.fg("muted", this.theme.bold("Content")), 0, 0));
134
+ content.addChild(new Spacer(1));
135
+ content.addChild(new Markdown(this.skill.content || this.theme.fg("dim", "(empty skill body)"), 0, 0, getMarkdownTheme()));
136
+ const lines = content.render(innerWidth);
137
+ this.lastInnerWidth = innerWidth;
138
+ this.lastContentLines = lines;
139
+ return lines;
140
+ }
141
+ footer(innerWidth, visibleHeight, totalLines) {
142
+ const maxScroll = Math.max(0, totalLines - visibleHeight);
143
+ const scroll = maxScroll > 0 ? this.theme.fg("dim", ` ${glyphs().bullet.trim()} ${this.scrollOffset + 1}-${Math.min(totalLines, this.scrollOffset + visibleHeight)}/${totalLines}`) : "";
144
+ const hints = [["-/=", "page"]];
145
+ hints.push(["ctrl+x", "enable/disable"]);
146
+ if (isDeletableSkill(this.skill))
147
+ hints.push(["alt+e", "edit"], ["alt+r", "rename"], ["backspace", "delete"]);
148
+ return truncateToWidth(`${skillKeyHints(this.theme, hints)}${scroll}`, innerWidth, this.theme.fg("dim", "..."));
149
+ }
150
+ render(width) {
151
+ if (width < 8)
152
+ return [];
153
+ const innerWidth = Math.max(1, width - 4);
154
+ const visibleHeight = Math.max(1, this.maxHeight() - 3);
155
+ const contentLines = this.buildContentLines(innerWidth);
156
+ const maxScroll = Math.max(0, contentLines.length - visibleHeight);
157
+ this.scrollOffset = Math.max(0, Math.min(this.scrollOffset, maxScroll));
158
+ const visible = contentLines.slice(this.scrollOffset, this.scrollOffset + visibleHeight);
159
+ return renderFrame(this.theme, width, [...visible, this.footer(innerWidth, visibleHeight, contentLines.length)]);
160
+ }
161
+ handleInput(data) {
162
+ const visibleHeight = Math.max(1, this.maxHeight() - 3);
163
+ const total = this.lastContentLines.length || this.buildContentLines(this.lastInnerWidth).length;
164
+ const maxScroll = Math.max(0, total - visibleHeight);
165
+ if (matchesKey(data, Key.up))
166
+ this.scrollOffset = Math.max(0, this.scrollOffset - 1);
167
+ else if (matchesKey(data, Key.down))
168
+ this.scrollOffset = Math.min(maxScroll, this.scrollOffset + 1);
169
+ else if (matchesKey(data, "-") || matchesKey(data, Key.pageUp))
170
+ this.scrollOffset = Math.max(0, this.scrollOffset - visibleHeight);
171
+ else if (matchesKey(data, "=") || matchesKey(data, Key.pageDown))
172
+ this.scrollOffset = Math.min(maxScroll, this.scrollOffset + visibleHeight);
173
+ else if (matchesKey(data, Key.home))
174
+ this.scrollOffset = 0;
175
+ else if (matchesKey(data, Key.end))
176
+ this.scrollOffset = maxScroll;
177
+ }
178
+ }
179
+
180
+ export class SkillEditorView {
181
+ editor;
182
+ initialText;
183
+ proxyTui;
184
+ skill;
185
+ theme;
186
+ realTui;
187
+ onSave;
188
+ onCancel;
189
+ virtualRows = 24;
190
+ _focused = false;
191
+ message;
192
+ get focused() {
193
+ return this._focused;
194
+ }
195
+ set focused(value) {
196
+ this._focused = value;
197
+ this.editor.focused = value;
198
+ }
199
+ constructor(skill, theme, realTui, initialText, onSave, onCancel) {
200
+ this.skill = skill;
201
+ this.theme = theme;
202
+ this.realTui = realTui;
203
+ this.onSave = onSave;
204
+ this.onCancel = onCancel;
205
+ this.initialText = initialText;
206
+ const self = this;
207
+ this.proxyTui = { requestRender: () => realTui.requestRender(), get terminal() {
208
+ return { ...realTui.terminal, rows: Math.max(1, self.virtualRows) };
209
+ } };
210
+ this.editor = new Editor(this.proxyTui, getEditorTheme(theme), { autocompleteMaxVisible: 6 });
211
+ this.editor.setText(initialText);
212
+ }
213
+ setSkill(skill) {
214
+ this.skill = skill;
215
+ }
216
+ setMessage(text, tone) {
217
+ this.message = { text, tone };
218
+ }
219
+ isDirty() {
220
+ return this.editor.getText() !== this.initialText;
221
+ }
222
+ invalidate() {
223
+ this.editor.invalidate();
224
+ }
225
+ targetHeight() {
226
+ return Math.max(10, Math.floor(this.realTui.terminal.rows * 0.78));
227
+ }
228
+ rowsForVisibleEditorLines(targetVisibleLines) {
229
+ let rows = 5;
230
+ while (Math.max(5, Math.floor(rows * 0.3)) < targetVisibleLines && rows < 1000)
231
+ rows += 1;
232
+ return rows;
233
+ }
234
+ render(width) {
235
+ const innerWidth = Math.max(1, width - 4);
236
+ const lines = [
237
+ skillEntityTitle(this.theme, `Edit ${this.skill.name}`),
238
+ this.theme.fg("muted", skillLocation(this.skill)),
239
+ this.theme.fg("dim", `Name is immutable here: ${this.skill.name}`)
240
+ ];
241
+ if (this.message)
242
+ lines.push("", toneText(this.theme, this.message.tone, this.message.text));
243
+ const targetInnerLines = Math.max(1, this.targetHeight() - 2);
244
+ const staticLineCount = lines.length + 3;
245
+ const editorBlockLines = Math.max(7, targetInnerLines - staticLineCount);
246
+ this.virtualRows = this.rowsForVisibleEditorLines(Math.max(5, editorBlockLines - 2));
247
+ lines.push("", ...this.editor.render(innerWidth), "", truncateToWidth(skillKeyHints(this.theme, [["alt+s", "save"]]), innerWidth, this.theme.fg("dim", "...")));
248
+ while (lines.length < targetInnerLines)
249
+ lines.splice(Math.max(0, lines.length - 1), 0, "");
250
+ return renderFrame(this.theme, width, lines.slice(0, targetInnerLines));
251
+ }
252
+ handleInput(data) {
253
+ if (matchesKey(data, Key.escape)) {
254
+ this.onCancel();
255
+ return;
256
+ }
257
+ if (matchesKey(data, Key.alt("s")) || matchesKey(data, Key.ctrl("s"))) {
258
+ this.onSave(this.editor.getText());
259
+ return;
260
+ }
261
+ if (this.message?.tone === "error")
262
+ this.message = undefined;
263
+ this.editor.handleInput(data);
264
+ }
265
+ }
@@ -0,0 +1,31 @@
1
+ export const PACKAGE_ID = "@vanillagreen/dm-skills-manager";
2
+ export const INSTALL_SYMBOL = Symbol.for("kendex.dm-skills-manager.installed");
3
+ export const STARTUP_PATCH_SYMBOL = Symbol.for("kendex.dm-skills-manager.startup-patch");
4
+ export const STARTUP_HIDE_ENABLED_SYMBOL = Symbol.for("kendex.dm-skills-manager.hide-startup-skills");
5
+ export const KENDEX_MODAL_LOCK_SYMBOL = Symbol.for("kendex.dm.modal-lock");
6
+ export const DEFAULT_POPUP_WIDTH = "82%";
7
+ export const DEFAULT_POPUP_MAX_HEIGHT = "86%";
8
+ export const DEFAULT_LIST_ROWS = 14;
9
+ const ANSI_GREEN_FG = "\x1B[32m";
10
+ const ANSI_YELLOW_FG = "\x1B[33m";
11
+ const ANSI_FG_RESET = "\x1B[39m";
12
+ export function ansiGreen(text) {
13
+ return `${ANSI_GREEN_FG}${text}${ANSI_FG_RESET}`;
14
+ }
15
+ export function ansiYellow(text) {
16
+ return `${ANSI_YELLOW_FG}${text}${ANSI_FG_RESET}`;
17
+ }
18
+ export const GENERATE_SKILL_SYSTEM_PROMPT = `You create production-ready DM Agent skills.
19
+
20
+ Return only a complete SKILL.md file. Do not wrap it in fences. Do not add commentary.
21
+
22
+ Rules:
23
+ - Start with YAML frontmatter containing name and description.
24
+ - The frontmatter name must exactly match the provided skill_slug.
25
+ - The description is the trigger surface: state what the skill does and when DM should use it.
26
+ - Include allowed-tools only if provided, as one space-delimited string.
27
+ - Keep the body concise, operational, and reusable.
28
+ - Prefer workflows, decision rules, output expectations, constraints, edge cases, and final checks.
29
+ - Do not add placeholders, TODOs, fake files, or ungrounded scripts/references.
30
+ - Use relative paths only if the user explicitly grounded extra files.
31
+ - Do not mention skill-authoring infrastructure, package managers, or this generation process.`;
@@ -0,0 +1,20 @@
1
+ export function isAbortError(error) {
2
+ if (!error || typeof error !== "object")
3
+ return false;
4
+ const name = "name" in error ? String(error.name) : "";
5
+ const message = "message" in error ? String(error.message) : "";
6
+ return name === "AbortError" || message.toLowerCase().includes("aborted");
7
+ }
8
+ export async function resolveSkillDraft(generate, fallback, signal, onFallback) {
9
+ if (signal?.aborted)
10
+ return null;
11
+ try {
12
+ const draft = await generate();
13
+ return signal?.aborted ? null : draft;
14
+ } catch (error) {
15
+ if (signal?.aborted || isAbortError(error))
16
+ return null;
17
+ onFallback(error);
18
+ return fallback();
19
+ }
20
+ }