@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,54 @@
1
+ import { relative, dirname } from "node:path";
2
+ import { getAgentDir, SettingsManager } from "@duckmind/dm-coding-agent";
3
+ import { findProjectDmDir, projectSettingsTrusted } from "./paths.js";
4
+ function updatePatterns(current, pattern, enabled) {
5
+ const updated = current.filter((entry) => {
6
+ const stripped = entry.startsWith("!") || entry.startsWith("+") || entry.startsWith("-") ? entry.slice(1) : entry;
7
+ return stripped !== pattern;
8
+ });
9
+ updated.push(`${enabled ? "+" : "-"}${pattern}`);
10
+ return updated;
11
+ }
12
+ function getTopLevelPattern(skill, cwd) {
13
+ const baseDir = skill.scope === "project" ? findProjectDmDir(cwd) : getAgentDir();
14
+ return relative(baseDir, skill.path);
15
+ }
16
+ function getPackagePattern(skill) {
17
+ const baseDir = skill.baseDir ?? dirname(skill.path);
18
+ return relative(baseDir, skill.path);
19
+ }
20
+ function hasPackageFilters(pkg) {
21
+ return pkg.extensions !== undefined || pkg.skills !== undefined || pkg.prompts !== undefined || pkg.themes !== undefined;
22
+ }
23
+ export async function setSkillEnabled(cwd, skill, enabled) {
24
+ if (skill.scope === "temporary")
25
+ throw new Error("Temporary skills cannot be toggled.");
26
+ if (skill.scope === "project" && !projectSettingsTrusted(cwd))
27
+ throw new Error("Project skills cannot be toggled before project trust is active.");
28
+ const settingsManager = SettingsManager.create(cwd, getAgentDir(), { projectTrusted: projectSettingsTrusted(cwd) });
29
+ if (skill.origin === "top-level") {
30
+ const settings = skill.scope === "project" ? settingsManager.getProjectSettings() : settingsManager.getGlobalSettings();
31
+ const updated = updatePatterns([...settings.skills ?? []], getTopLevelPattern(skill, cwd), enabled);
32
+ if (skill.scope === "project")
33
+ settingsManager.setProjectSkillPaths(updated);
34
+ else
35
+ settingsManager.setSkillPaths(updated);
36
+ await settingsManager.flush();
37
+ return;
38
+ }
39
+ const settings = skill.scope === "project" ? settingsManager.getProjectSettings() : settingsManager.getGlobalSettings();
40
+ const packages = [...settings.packages ?? []];
41
+ const packageIndex = packages.findIndex((pkg) => (typeof pkg === "string" ? pkg : pkg.source) === skill.source);
42
+ if (packageIndex === -1)
43
+ throw new Error("Could not find the package settings entry for this skill.");
44
+ const packageEntry = packages[packageIndex];
45
+ const packageConfig = typeof packageEntry === "string" ? { source: packageEntry } : { ...packageEntry };
46
+ const updated = updatePatterns([...packageConfig.skills ?? []], getPackagePattern(skill), enabled);
47
+ packageConfig.skills = updated.length > 0 ? updated : undefined;
48
+ packages[packageIndex] = hasPackageFilters(packageConfig) ? packageConfig : packageConfig.source;
49
+ if (skill.scope === "project")
50
+ settingsManager.setProjectPackages(packages);
51
+ else
52
+ settingsManager.setPackages(packages);
53
+ await settingsManager.flush();
54
+ }
@@ -0,0 +1,14 @@
1
+ export const EMPTY_REGISTRY = {
2
+ skills: [],
3
+ allSkills: [],
4
+ byName: new Map
5
+ };
6
+ export const LOCATION_OPTIONS = [
7
+ { value: "project", label: "Project", description: "Save in this project's .dm/skills directory." },
8
+ { value: "global", label: "Global", description: "Save in your user-level DM skills directory." }
9
+ ];
10
+ export const CREATE_STEPS = [
11
+ { id: "name", title: "Name", hint: "Use lowercase letters, numbers, and hyphens, for example react-review.", optional: false, kind: "text" },
12
+ { id: "description", title: "Description", hint: "Describe what the skill does and when it should be used. Specific trigger language works best.", optional: false, kind: "text" },
13
+ { id: "location", title: "Visibility", hint: "Choose whether the skill is project-local or available in all DM sessions.", optional: false, kind: "choice", options: LOCATION_OPTIONS }
14
+ ];
@@ -0,0 +1,121 @@
1
+ import { truncateToWidth, visibleWidth } from "@duckmind/dm-tui";
2
+ import { ansiGreen, ansiYellow, KENDEX_MODAL_LOCK_SYMBOL } from "./constants.js";
3
+ import { frameGlyphs, glyphs } from "./glyphs.js";
4
+ export function inlineLine(text) {
5
+ return text.replace(/[\r\n]+/g, " ").replace(/\t/g, " ");
6
+ }
7
+ export function acquirekendexModalLock() {
8
+ const host = globalThis;
9
+ const existing = host[KENDEX_MODAL_LOCK_SYMBOL];
10
+ const lock = existing && typeof existing.depth === "number" ? existing : { depth: 0 };
11
+ host[KENDEX_MODAL_LOCK_SYMBOL] = lock;
12
+ lock.depth += 1;
13
+ let released = false;
14
+ return () => {
15
+ if (released)
16
+ return;
17
+ released = true;
18
+ lock.depth = Math.max(0, lock.depth - 1);
19
+ };
20
+ }
21
+ export function padAnsi(text, width) {
22
+ const safeWidth = Math.max(0, width);
23
+ const truncated = truncateToWidth(inlineLine(text), safeWidth, "");
24
+ return `${truncated}${" ".repeat(Math.max(0, safeWidth - visibleWidth(truncated)))}`;
25
+ }
26
+ export function toneText(theme, tone, text) {
27
+ if (tone === "error")
28
+ return theme.fg("error", text);
29
+ if (tone === "success")
30
+ return theme.fg("success", text);
31
+ return theme.fg("dim", text);
32
+ }
33
+ export function skillEntityTitle(theme, label) {
34
+ return theme.fg("text", theme.bold(label));
35
+ }
36
+ export function skillSectionTitle(theme, label) {
37
+ return theme.fg("muted", theme.bold(label));
38
+ }
39
+ export function skillSelectedLine(theme, line, width) {
40
+ return theme.bg("selectedBg", padAnsi(line, width));
41
+ }
42
+ export function skillKeyHints(theme, hints) {
43
+ return hints.map(([key, description]) => `${ansiYellow(key)} ${theme.fg("dim", description)}`).join(theme.fg("dim", ` ${glyphs().bullet.trim()} `));
44
+ }
45
+ function frameLine(theme, line, innerWidth) {
46
+ const clipped = truncateToWidth(inlineLine(line), innerWidth, theme.fg("dim", "..."));
47
+ const frame = frameGlyphs();
48
+ return `${theme.fg("borderAccent", `${frame.v} `)}${padAnsi(clipped, innerWidth)}${theme.fg("borderAccent", ` ${frame.v}`)}`;
49
+ }
50
+ function fitFrameBody(theme, lines, fixedInnerRows) {
51
+ if (fixedInnerRows === undefined)
52
+ return lines;
53
+ const rowCount = Math.max(1, Math.floor(fixedInnerRows));
54
+ if (lines.length > rowCount) {
55
+ const hidden = lines.length - rowCount + 1;
56
+ return [...lines.slice(0, Math.max(0, rowCount - 1)), theme.fg("dim", `↓ ${hidden} more line(s)`)].slice(0, rowCount);
57
+ }
58
+ if (lines.length < rowCount)
59
+ return [...lines, ...Array.from({ length: rowCount - lines.length }, () => "")];
60
+ return lines;
61
+ }
62
+ export function renderFrame(theme, width, lines, fixedInnerRows, title = "", right = "") {
63
+ const body = fitFrameBody(theme, lines, fixedInnerRows);
64
+ if (width < 6)
65
+ return body.map((line) => truncateToWidth(inlineLine(line), width, ""));
66
+ const innerWidth = Math.max(1, width - 4);
67
+ const frame = frameGlyphs();
68
+ const top = () => {
69
+ const contentWidth = innerWidth + 2;
70
+ if (!title && !right)
71
+ return theme.fg("borderAccent", `${frame.tl}${frame.h.repeat(contentWidth)}${frame.tr}`);
72
+ const rightPlain = right ? ` ${right} ` : "";
73
+ const titleBudget = Math.max(1, contentWidth - visibleWidth(rightPlain) - 1);
74
+ const titlePlain = title ? ` ${truncateToWidth(title, Math.max(1, titleBudget - 2), glyphs().ellipsis)} ` : "";
75
+ const fill = Math.max(1, contentWidth - visibleWidth(titlePlain) - visibleWidth(rightPlain));
76
+ return `${theme.fg("borderAccent", frame.tl)}${titlePlain ? ansiGreen(titlePlain) : ""}${theme.fg("borderAccent", frame.h.repeat(fill))}${rightPlain ? theme.fg("dim", rightPlain) : ""}${theme.fg("borderAccent", frame.tr)}`;
77
+ };
78
+ return [
79
+ top(),
80
+ ...body.map((line) => frameLine(theme, line, innerWidth)),
81
+ theme.fg("borderAccent", `${frame.bl}${frame.h.repeat(innerWidth + 2)}${frame.br}`)
82
+ ].map((line) => truncateToWidth(inlineLine(line), width, ""));
83
+ }
84
+ function centerLines(lines, width) {
85
+ const renderedWidth = lines.reduce((max, line) => Math.max(max, visibleWidth(line)), 0);
86
+ const leftPad = Math.max(0, Math.floor((width - renderedWidth) / 2));
87
+ return leftPad === 0 ? lines : lines.map((line) => `${" ".repeat(leftPad)}${line}`);
88
+ }
89
+ export function renderCenteredDialog(theme, width, lines, maxInnerWidth = 68) {
90
+ if (width < 8)
91
+ return lines.map((line) => truncateToWidth(line, width, ""));
92
+ const innerWidth = Math.max(1, Math.min(width - 4, maxInnerWidth));
93
+ const frame = frameGlyphs();
94
+ const framed = [
95
+ theme.fg("borderAccent", `${frame.tl}${frame.h.repeat(innerWidth + 2)}${frame.tr}`),
96
+ ...lines.map((line) => frameLine(theme, line, innerWidth)),
97
+ theme.fg("borderAccent", `${frame.bl}${frame.h.repeat(innerWidth + 2)}${frame.br}`)
98
+ ];
99
+ return centerLines(framed, width);
100
+ }
101
+ export function getEditorTheme(theme) {
102
+ return {
103
+ borderColor: (text) => theme.fg("accent", text),
104
+ selectList: {
105
+ selectedPrefix: (text) => theme.fg("accent", text),
106
+ selectedText: (text) => theme.bg("selectedBg", theme.fg("text", text)),
107
+ description: (text) => theme.fg("muted", text),
108
+ scrollInfo: (text) => theme.fg("dim", text),
109
+ noMatch: (text) => theme.fg("warning", text)
110
+ }
111
+ };
112
+ }
113
+ export function scopeLabel(skill) {
114
+ return skill.scope === "project" ? "project" : skill.scope === "user" ? "global" : "temporary";
115
+ }
116
+ export function packageLabel(skill) {
117
+ return skill.origin === "package" && skill.source ? skill.source : undefined;
118
+ }
119
+ export function skillLocation(skill) {
120
+ return skill.origin === "package" ? skill.source : skill.path;
121
+ }
@@ -0,0 +1,111 @@
1
+ import { INSTALL_SYMBOL } from "./skills-manager/constants.js";
2
+ import { createSkillFromAnswers } from "./skills-manager/creation.js";
3
+ import { showSkillsManager } from "./skills-manager/dialog.js";
4
+ import { recordProjectTrust } from "./skills-manager/paths.js";
5
+ import { deleteSkill, loadSkillRegistry } from "./skills-manager/registry.js";
6
+ import { settingBoolean, updatePackageConfig } from "./skills-manager/settings.js";
7
+ import { patchInteractiveModeStartupSkillsBlock, setStartupHideEnabled } from "./skills-manager/startup.js";
8
+ import { setSkillEnabled } from "./skills-manager/toggle.js";
9
+ import { EMPTY_REGISTRY } from "./skills-manager/types.js";
10
+ function errorMessage(error) {
11
+ const message = error instanceof Error ? error.message : String(error);
12
+ return message.length > 180 ? `${message.slice(0, 179)}…` : message;
13
+ }
14
+ function insertNativeSkillCommand(ctx, skill) {
15
+ ctx.ui.pasteToEditor(`/skill:${skill.name}
16
+ `);
17
+ }
18
+ function dmThinkingLevel(value) {
19
+ if (value === "max")
20
+ return "xhigh";
21
+ return value === "off" || value === "minimal" || value === "low" || value === "medium" || value === "high" || value === "xhigh" ? value : "off";
22
+ }
23
+ export default function skillsManager(pi) {
24
+ const guard = pi;
25
+ if (guard[INSTALL_SYMBOL])
26
+ return;
27
+ guard[INSTALL_SYMBOL] = true;
28
+ patchInteractiveModeStartupSkillsBlock();
29
+ setStartupHideEnabled(settingBoolean("enabled", true) && settingBoolean("hideStartupSkillsBlock", true));
30
+ let registry = EMPTY_REGISTRY;
31
+ const enabledAtLoad = settingBoolean("enabled", true);
32
+ if (!enabledAtLoad) {
33
+ const enableRecovery = async (ctx) => {
34
+ updatePackageConfig(ctx.cwd, { enabled: true });
35
+ ctx.ui.notify("Skills Manager enabled. Reloading...", "info");
36
+ await ctx.reload();
37
+ };
38
+ pi.registerCommand("skill", {
39
+ description: "Skills manager recovery command.",
40
+ handler: async (args, ctx) => {
41
+ if (args.trim().toLowerCase() !== "enable") {
42
+ ctx.ui.notify("Skills Manager is disabled. Run /skill:enable, then /reload.", "warning");
43
+ return;
44
+ }
45
+ await enableRecovery(ctx);
46
+ }
47
+ });
48
+ pi.registerCommand("skill:enable", {
49
+ description: "Re-enable the skills manager",
50
+ handler: async (_args, ctx) => enableRecovery(ctx)
51
+ });
52
+ return;
53
+ }
54
+ async function refreshRegistry(cwd) {
55
+ registry = await loadSkillRegistry(cwd);
56
+ return registry;
57
+ }
58
+ async function prepareSession(ctx) {
59
+ recordProjectTrust(ctx);
60
+ setStartupHideEnabled(settingBoolean("enabled", true, ctx.cwd) && settingBoolean("hideStartupSkillsBlock", true, ctx.cwd));
61
+ try {
62
+ await refreshRegistry(ctx.cwd);
63
+ } catch (error) {
64
+ registry = EMPTY_REGISTRY;
65
+ ctx.ui.notify(`Skills Manager failed to load registry: ${errorMessage(error)}`, "error");
66
+ }
67
+ return false;
68
+ }
69
+ pi.registerCommand("skill", {
70
+ description: "DM skills manager view. Native skills remain /skill:name.",
71
+ handler: async (args, ctx) => {
72
+ const rawArgs = args.trim();
73
+ const trimmed = rawArgs.toLowerCase();
74
+ if (trimmed === "enable") {
75
+ updatePackageConfig(ctx.cwd, { enabled: true });
76
+ ctx.ui.notify("Skills Manager already enabled.", "info");
77
+ return;
78
+ }
79
+ if (trimmed === "disable") {
80
+ updatePackageConfig(ctx.cwd, { enabled: false });
81
+ ctx.ui.notify("Skills Manager disabled. Run /reload to unload commands/hooks.", "info");
82
+ return;
83
+ }
84
+ if (rawArgs) {
85
+ ctx.ui.notify("Use /skill:name for native skill invocation, or /skill with no arguments for the manager.", "warning");
86
+ return;
87
+ }
88
+ if (!ctx.hasUI) {
89
+ ctx.ui.notify("/skill manager requires interactive mode", "warning");
90
+ return;
91
+ }
92
+ try {
93
+ await refreshRegistry(ctx.cwd);
94
+ } catch (error) {
95
+ ctx.ui.notify(`Failed to load skills list: ${errorMessage(error)}`, "error");
96
+ return;
97
+ }
98
+ const selection = await showSkillsManager(ctx, registry, {
99
+ onCreate: async (answers, signal) => await createSkillFromAnswers(ctx, answers, { thinkingLevel: dmThinkingLevel(pi.getThinkingLevel()), signal }),
100
+ onDelete: async (skill) => await deleteSkill(ctx, skill),
101
+ onToggle: async (skill, enabled) => await setSkillEnabled(ctx.cwd, skill, enabled),
102
+ onRefresh: async () => await refreshRegistry(ctx.cwd)
103
+ });
104
+ if (selection)
105
+ insertNativeSkillCommand(ctx, selection);
106
+ }
107
+ });
108
+ pi.on("session_start", async (_event, ctx) => {
109
+ await prepareSession(ctx);
110
+ });
111
+ }
@@ -0,0 +1,121 @@
1
+ {
2
+ "name": "dm-skills-manager",
3
+ "version": "2.0.0",
4
+ "description": "Skills manager for DM with browsing, preview, insertion, creation, editing, renaming, deletion, and enablement controls.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "keywords": [
8
+ "duckmind",
9
+ "dm",
10
+ "dm-package",
11
+ "dm-extension",
12
+ "skills",
13
+ "manager"
14
+ ],
15
+ "dm": {
16
+ "extensions": [
17
+ "./extensions/skills-manager.js"
18
+ ],
19
+ "image": "https://raw.githubusercontent.com/vanillagreencom/kendex/9e4bc7e8ffa832e8d86b6e849cff1a108a679225/pi-extensions/pi-skills-manager/assets/skills-manager.png"
20
+ },
21
+ "kendex": {
22
+ "extensionManager": {
23
+ "displayName": "Skills Manager",
24
+ "settings": [
25
+ {
26
+ "key": "enabled",
27
+ "label": "Enable skills manager",
28
+ "description": "Register the /skill manager view, startup skill-list hiding, and skills management UI. If disabled, /skill enable remains available as a recovery command. Native /skill:name commands stay controlled by DM's Skill commands setting.",
29
+ "type": "boolean",
30
+ "default": true,
31
+ "category": "General",
32
+ "apply": "reload",
33
+ "requiresReload": true
34
+ },
35
+ {
36
+ "key": "glyphStyle",
37
+ "label": "Glyph style",
38
+ "description": "Glyphs for kendex-owned UI chrome. Unicode is the default; ASCII uses plain box/status/separator characters for terminal compatibility. DM tool renderer's global override wins when set.",
39
+ "type": "enum",
40
+ "enumValues": [
41
+ "unicode",
42
+ "ascii"
43
+ ],
44
+ "default": "unicode",
45
+ "category": "Rendering",
46
+ "apply": "live"
47
+ },
48
+ {
49
+ "key": "hideStartupSkillsBlock",
50
+ "label": "Hide startup skills block",
51
+ "description": "Hide DM's built-in startup [Skills] list so skills are managed through the dedicated menu instead.",
52
+ "type": "boolean",
53
+ "default": true,
54
+ "category": "Integration",
55
+ "apply": "reload",
56
+ "requiresReload": true
57
+ },
58
+ {
59
+ "key": "aiGenerationEnabled",
60
+ "label": "AI skill generation",
61
+ "description": "Use the currently selected model/thinking level to draft new SKILL.md files. If unavailable or disabled, a deterministic fallback template is used.",
62
+ "type": "boolean",
63
+ "default": true,
64
+ "category": "Creation",
65
+ "apply": "live"
66
+ },
67
+ {
68
+ "key": "defaultCreateLocation",
69
+ "label": "Default create location",
70
+ "description": "Initial visibility choice when creating a skill.",
71
+ "type": "enum",
72
+ "enumValues": [
73
+ "project",
74
+ "global"
75
+ ],
76
+ "default": "project",
77
+ "category": "Creation",
78
+ "apply": "live"
79
+ },
80
+ {
81
+ "key": "popupWidth",
82
+ "label": "Popup width",
83
+ "description": "Preferred overlay width. Accepts a number of columns or a percentage string such as 82%.",
84
+ "type": "string",
85
+ "default": "82%",
86
+ "category": "UI",
87
+ "apply": "live"
88
+ },
89
+ {
90
+ "key": "popupMaxHeight",
91
+ "label": "Popup max height",
92
+ "description": "Maximum overlay height. Accepts a number of rows or a percentage string.",
93
+ "type": "string",
94
+ "default": "86%",
95
+ "category": "UI",
96
+ "apply": "live"
97
+ },
98
+ {
99
+ "key": "listRows",
100
+ "label": "Visible list rows",
101
+ "description": "Maximum list rows shown before scrolling in browse mode. Short terminals shrink this to keep popup controls visible.",
102
+ "type": "number",
103
+ "default": 14,
104
+ "category": "UI",
105
+ "apply": "live"
106
+ }
107
+ ]
108
+ }
109
+ },
110
+ "peerDependenciesMeta": {
111
+ "@duckmind/dm-ai": {
112
+ "optional": true
113
+ },
114
+ "@duckmind/dm-coding-agent": {
115
+ "optional": true
116
+ },
117
+ "@duckmind/dm-tui": {
118
+ "optional": true
119
+ }
120
+ }
121
+ }
@@ -0,0 +1,5 @@
1
+ # Security Policy
2
+
3
+ Please report suspected vulnerabilities through GitHub private vulnerability reporting for this repository. Do not post exploit details, secrets, or proof-of-concept payloads in public issues or pull requests.
4
+
5
+ If private vulnerability reporting is unavailable for your account or this repository, open a minimal public issue asking for a private contact path without including technical details.
@@ -0,0 +1,65 @@
1
+ export class ActivityMonitor {
2
+ entries = [];
3
+ maxEntries = 10;
4
+ listeners = new Set;
5
+ rateLimitInfo = { used: 0, max: 10, oldestTimestamp: null, windowMs: 60000 };
6
+ nextId = 1;
7
+ logStart(partial) {
8
+ const id = `act-${this.nextId++}`;
9
+ const entry = {
10
+ ...partial,
11
+ id,
12
+ startTime: Date.now(),
13
+ status: null
14
+ };
15
+ this.entries.push(entry);
16
+ if (this.entries.length > this.maxEntries) {
17
+ this.entries.shift();
18
+ }
19
+ this.notify();
20
+ return id;
21
+ }
22
+ logComplete(id, status) {
23
+ const entry = this.entries.find((e) => e.id === id);
24
+ if (entry) {
25
+ entry.endTime = Date.now();
26
+ entry.status = status;
27
+ this.notify();
28
+ }
29
+ }
30
+ logError(id, error) {
31
+ const entry = this.entries.find((e) => e.id === id);
32
+ if (entry) {
33
+ entry.endTime = Date.now();
34
+ entry.error = error;
35
+ this.notify();
36
+ }
37
+ }
38
+ getEntries() {
39
+ return this.entries;
40
+ }
41
+ getRateLimitInfo() {
42
+ return this.rateLimitInfo;
43
+ }
44
+ updateRateLimit(info) {
45
+ this.rateLimitInfo = info;
46
+ this.notify();
47
+ }
48
+ onUpdate(callback) {
49
+ this.listeners.add(callback);
50
+ return () => this.listeners.delete(callback);
51
+ }
52
+ clear() {
53
+ this.entries = [];
54
+ this.rateLimitInfo = { used: 0, max: 10, oldestTimestamp: null, windowMs: 60000 };
55
+ this.notify();
56
+ }
57
+ notify() {
58
+ for (const cb of this.listeners) {
59
+ try {
60
+ cb();
61
+ } catch {}
62
+ }
63
+ }
64
+ }
65
+ export const activityMonitor = new ActivityMonitor;
@@ -0,0 +1,158 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { activityMonitor } from "./activity.js";
3
+ import { redactCredential, resolveCredential } from "./credential-source.js";
4
+ import { getWebSearchConfigPath } from "./utils.js";
5
+ const ANYSEARCH_API_URL = "https://api.anysearch.com/v1/search";
6
+ const CONFIG_PATH = getWebSearchConfigPath();
7
+ const SEARCH_TIMEOUT_MS = 30000;
8
+ let cachedConfig = null;
9
+ function loadConfig() {
10
+ if (cachedConfig)
11
+ return cachedConfig;
12
+ if (!existsSync(CONFIG_PATH)) {
13
+ cachedConfig = {};
14
+ return cachedConfig;
15
+ }
16
+ const raw = readFileSync(CONFIG_PATH, "utf-8");
17
+ let parsed;
18
+ try {
19
+ parsed = JSON.parse(raw);
20
+ } catch (err) {
21
+ const message = err instanceof Error ? err.message : String(err);
22
+ throw new Error(`Failed to parse ${CONFIG_PATH}: ${message}`);
23
+ }
24
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
25
+ throw new Error(`Invalid config in ${CONFIG_PATH}: expected a JSON object`);
26
+ }
27
+ cachedConfig = parsed;
28
+ return cachedConfig;
29
+ }
30
+ async function getApiKey(signal) {
31
+ return resolveCredential({
32
+ provider: "AnySearch",
33
+ configuredValue: loadConfig().anysearchApiKey,
34
+ environmentValue: process.env.ANYSEARCH_API_KEY,
35
+ signal
36
+ });
37
+ }
38
+ function normalizeCount(value) {
39
+ if (typeof value !== "number" || !Number.isFinite(value))
40
+ return 5;
41
+ return Math.max(1, Math.min(Math.floor(value), 20));
42
+ }
43
+ function errorMessage(err) {
44
+ return err instanceof Error ? err.message : String(err);
45
+ }
46
+ function invalidResponse(message) {
47
+ return new Error(`AnySearch API returned invalid response: ${message}`);
48
+ }
49
+ function parseResponse(value) {
50
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
51
+ throw invalidResponse("expected an object envelope");
52
+ }
53
+ const envelope = value;
54
+ if (envelope.code !== 0)
55
+ throw invalidResponse("expected code 0");
56
+ if (!envelope.data || typeof envelope.data !== "object" || Array.isArray(envelope.data)) {
57
+ throw invalidResponse("expected data object");
58
+ }
59
+ const data = envelope.data;
60
+ if (!Array.isArray(data.results))
61
+ throw invalidResponse("expected data.results array");
62
+ if (!data.metadata || typeof data.metadata !== "object" || Array.isArray(data.metadata)) {
63
+ throw invalidResponse("expected data.metadata object");
64
+ }
65
+ const results = [];
66
+ for (const [index, value] of data.results.entries()) {
67
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
68
+ throw invalidResponse(`expected data.results[${index}] object`);
69
+ }
70
+ const result = value;
71
+ const { title, url, snippet, content } = result;
72
+ if (typeof title !== "string")
73
+ throw invalidResponse(`expected data.results[${index}].title string`);
74
+ if (typeof url !== "string")
75
+ throw invalidResponse(`expected data.results[${index}].url string`);
76
+ if (typeof snippet !== "string")
77
+ throw invalidResponse(`expected data.results[${index}].snippet string`);
78
+ if (content !== undefined && content !== null && typeof content !== "string") {
79
+ throw invalidResponse(`expected data.results[${index}].content string`);
80
+ }
81
+ if (!url)
82
+ throw invalidResponse(`expected data.results[${index}].url to be non-empty`);
83
+ results.push({ title, url, snippet, content: typeof content === "string" ? content : "" });
84
+ }
85
+ return { code: 0, data: { results, metadata: data.metadata } };
86
+ }
87
+ function buildAnswer(results) {
88
+ return results.map((result) => result.snippet ? `${result.snippet}
89
+ Source: ${result.title} (${result.url})` : `Source: ${result.title} (${result.url})`).join(`
90
+
91
+ `);
92
+ }
93
+ export function isAnySearchAvailable() {
94
+ return true;
95
+ }
96
+ export async function searchWithAnySearch(query, options = {}) {
97
+ const apiKey = await getApiKey(options.signal);
98
+ const numResults = normalizeCount(options.numResults);
99
+ const body = { query, max_results: numResults };
100
+ const activityId = activityMonitor.logStart({ type: "api", query });
101
+ let response;
102
+ try {
103
+ response = await fetch(ANYSEARCH_API_URL, {
104
+ method: "POST",
105
+ headers: {
106
+ ...apiKey ? { Authorization: `Bearer ${apiKey}` } : {},
107
+ "Content-Type": "application/json"
108
+ },
109
+ body: JSON.stringify(body),
110
+ signal: options.signal ? AbortSignal.any([AbortSignal.timeout(SEARCH_TIMEOUT_MS), options.signal]) : AbortSignal.timeout(SEARCH_TIMEOUT_MS)
111
+ });
112
+ } catch (err) {
113
+ const message = errorMessage(err);
114
+ const redactedMessage = redactCredential(message, apiKey);
115
+ if (redactedMessage.toLowerCase().includes("abort"))
116
+ activityMonitor.logComplete(activityId, 0);
117
+ else
118
+ activityMonitor.logError(activityId, redactedMessage);
119
+ if (redactedMessage === message)
120
+ throw err;
121
+ const redactedError = new Error(redactedMessage);
122
+ if (err instanceof Error)
123
+ redactedError.name = err.name;
124
+ throw redactedError;
125
+ }
126
+ if (!response.ok) {
127
+ activityMonitor.logComplete(activityId, response.status);
128
+ const errorText = redactCredential(await response.text(), apiKey);
129
+ throw new Error(`AnySearch API error ${response.status}: ${errorText.slice(0, 300)}`);
130
+ }
131
+ let rawData;
132
+ try {
133
+ rawData = await response.json();
134
+ } catch (err) {
135
+ activityMonitor.logComplete(activityId, response.status);
136
+ throw new Error(`AnySearch API returned invalid JSON: ${errorMessage(err)}`);
137
+ }
138
+ let data;
139
+ try {
140
+ data = parseResponse(rawData);
141
+ } catch (err) {
142
+ activityMonitor.logComplete(activityId, response.status);
143
+ throw err;
144
+ }
145
+ activityMonitor.logComplete(activityId, response.status);
146
+ const results = data.data.results.slice(0, numResults).map((result) => ({
147
+ title: result.title,
148
+ url: result.url,
149
+ snippet: result.snippet
150
+ }));
151
+ const mapped = { answer: buildAnswer(results), results };
152
+ if (options.includeContent) {
153
+ const inlineContent = data.data.results.slice(0, numResults).filter((result) => result.content.length > 0).map((result) => ({ url: result.url, title: result.title, content: result.content, error: null }));
154
+ if (inlineContent.length > 0)
155
+ mapped.inlineContent = inlineContent;
156
+ }
157
+ return mapped;
158
+ }