dryai 2.1.0 → 3.0.0

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 (47) hide show
  1. package/README.md +140 -122
  2. package/dest/cli.d.ts +68 -0
  3. package/dest/cli.js +147 -0
  4. package/dest/commands/skills/add.d.ts +4 -3
  5. package/dest/commands/skills/add.js +44 -12
  6. package/dest/commands/skills/index.d.ts +3 -3
  7. package/dest/commands/skills/index.js +19 -12
  8. package/dest/commands/skills/list.d.ts +2 -2
  9. package/dest/commands/skills/list.js +4 -3
  10. package/dest/commands/skills/rehash-all.d.ts +2 -2
  11. package/dest/commands/skills/rehash-all.js +6 -5
  12. package/dest/commands/skills/rehash.d.ts +2 -2
  13. package/dest/commands/skills/rehash.js +3 -2
  14. package/dest/commands/skills/remove.d.ts +2 -2
  15. package/dest/commands/skills/remove.js +3 -2
  16. package/dest/commands/skills/update-all.d.ts +2 -2
  17. package/dest/commands/skills/update-all.js +8 -7
  18. package/dest/commands/skills/update.d.ts +2 -2
  19. package/dest/commands/skills/update.js +6 -5
  20. package/dest/commands/sync.d.ts +6 -0
  21. package/dest/commands/sync.js +8 -0
  22. package/dest/lib/agent-definition-helpers.d.ts +74 -0
  23. package/dest/lib/agent-definition-helpers.js +68 -0
  24. package/dest/lib/agent-definitions.d.ts +333 -0
  25. package/dest/lib/agent-definitions.js +301 -0
  26. package/dest/lib/agent-types.d.ts +46 -0
  27. package/dest/lib/agent-types.js +1 -0
  28. package/dest/lib/agents.d.ts +81 -0
  29. package/dest/lib/agents.js +301 -0
  30. package/dest/lib/command-options.d.ts +1 -1
  31. package/dest/lib/command-options.js +1 -1
  32. package/dest/lib/context.d.ts +8 -25
  33. package/dest/lib/context.js +8 -26
  34. package/dest/lib/frontmatter.d.ts +27 -70
  35. package/dest/lib/frontmatter.js +23 -42
  36. package/dest/lib/object-helpers.d.ts +5 -0
  37. package/dest/lib/object-helpers.js +6 -0
  38. package/dest/lib/skills.d.ts +35 -93
  39. package/dest/lib/skills.js +66 -8
  40. package/dest/lib/sync.d.ts +7 -0
  41. package/dest/lib/sync.js +503 -0
  42. package/dest/main.js +6 -86
  43. package/package.json +3 -3
  44. package/dest/commands/install.d.ts +0 -3
  45. package/dest/commands/install.js +0 -4
  46. package/dest/lib/install.d.ts +0 -8
  47. package/dest/lib/install.js +0 -380
@@ -1,380 +0,0 @@
1
- import { Chalk } from 'chalk';
2
- import fs from 'fs-extra';
3
- import { glob } from 'glob';
4
- import path from 'node:path';
5
- import { commandFrontmatterSchema, compactObject, normalizeRuleMetadata, parseFrontmatter, renderMarkdown, ruleFrontmatterSchema, validateFrontmatter, } from './frontmatter.js';
6
- const ALL_INSTALL_EDITORS = ['copilot', 'cursor'];
7
- const chalk = new Chalk({ level: 3 });
8
- /**
9
- * Installs all generated command, rule, and skill outputs into the requested targets.
10
- */
11
- export async function installToTargets(context, { targetRoots }) {
12
- await ensureTargetDirectories(targetRoots);
13
- const installItems = [
14
- ...(await collectCommandInstallItems(context, { targetRoots })),
15
- ...(await collectRuleInstallItems(context, { targetRoots })),
16
- ...(await collectSkillInstallItems(context, { targetRoots })),
17
- ];
18
- const { installableItems, skippedItems } = collectConflictFilterResult(installItems);
19
- const appliedItems = [];
20
- for (const installItem of installableItems) {
21
- appliedItems.push(await applyInstallItem(installItem));
22
- }
23
- console.log(renderInstallReport(appliedItems, skippedItems));
24
- }
25
- /**
26
- * Ensures that all target root directories exist before generated files are written.
27
- */
28
- async function ensureTargetDirectories(targetRoots) {
29
- await Promise.all(Object.values(targetRoots).map((dirPath) => fs.ensureDir(dirPath)));
30
- }
31
- /**
32
- * Returns the markdown source files found directly under a source root.
33
- */
34
- async function collectMarkdownFiles(rootDir) {
35
- await fs.ensureDir(rootDir);
36
- const matches = await glob([path.join(rootDir, '*.md')]);
37
- return matches.sort();
38
- }
39
- /**
40
- * Writes one markdown file after rendering its frontmatter and body content.
41
- */
42
- async function writeMarkdownFile(filePath, metadata, body) {
43
- await fs.ensureDir(path.dirname(filePath));
44
- await fs.writeFile(filePath, renderMarkdown({ metadata, body }), 'utf8');
45
- }
46
- /**
47
- * Detects whether each editor target for one item will be installed or updated.
48
- */
49
- async function detectInstallChanges(installItem) {
50
- return Promise.all(installItem.targets.map(async (target) => ({
51
- editor: target.editor,
52
- changeType: (await fs.pathExists(target.outputPath))
53
- ? 'update'
54
- : 'install',
55
- })));
56
- }
57
- /**
58
- * Applies one install item and records the change type for each editor target.
59
- */
60
- async function applyInstallItem(installItem) {
61
- const changes = await detectInstallChanges(installItem);
62
- await installItem.install();
63
- return {
64
- item: installItem,
65
- changes,
66
- };
67
- }
68
- /**
69
- * Collects install operations for command sources after validating their frontmatter.
70
- */
71
- async function collectCommandInstallItems(context, { targetRoots }) {
72
- const commandFiles = await collectMarkdownFiles(context.sourceRoots.commands);
73
- const installItems = [];
74
- for (const filePath of commandFiles) {
75
- const fileName = path.basename(filePath, '.md');
76
- const rawContent = await fs.readFile(filePath, 'utf8');
77
- const { metadata, body } = parseFrontmatter(rawContent);
78
- const commandMetadata = validateFrontmatter({
79
- filePath,
80
- metadata,
81
- schema: commandFrontmatterSchema,
82
- });
83
- if (!commandMetadata) {
84
- continue;
85
- }
86
- const commandName = commandMetadata.name;
87
- const description = commandMetadata.description;
88
- const copilotPromptPath = path.join(targetRoots.copilotPrompts, `${fileName}.prompt.md`);
89
- const cursorSkillPath = path.join(targetRoots.cursorSkills, commandName, 'SKILL.md');
90
- const promptMetadata = compactObject({
91
- name: commandName,
92
- description,
93
- });
94
- const cursorMetadata = compactObject({
95
- name: commandName,
96
- description,
97
- 'disable-model-invocation': commandMetadata.cursor?.['disable-model-invocation'],
98
- });
99
- installItems.push({
100
- kind: 'command',
101
- editors: ALL_INSTALL_EDITORS,
102
- name: commandName,
103
- sourcePath: filePath,
104
- ownershipKeys: [
105
- `copilot-prompt-path:${copilotPromptPath}`,
106
- `cursor-skill-name:${commandName}`,
107
- ],
108
- targets: [
109
- { editor: 'copilot', outputPath: copilotPromptPath },
110
- { editor: 'cursor', outputPath: cursorSkillPath },
111
- ],
112
- install: async () => {
113
- await writeMarkdownFile(copilotPromptPath, promptMetadata, body);
114
- await writeMarkdownFile(cursorSkillPath, cursorMetadata, body);
115
- },
116
- });
117
- }
118
- return installItems;
119
- }
120
- /**
121
- * Collects install operations for rule sources after validating their frontmatter.
122
- */
123
- async function collectRuleInstallItems(context, { targetRoots }) {
124
- const ruleFiles = await collectMarkdownFiles(context.sourceRoots.rules);
125
- const installItems = [];
126
- for (const filePath of ruleFiles) {
127
- const fileName = path.basename(filePath, '.md');
128
- const rawContent = await fs.readFile(filePath, 'utf8');
129
- const { metadata, body } = parseFrontmatter(rawContent);
130
- const ruleMetadata = validateFrontmatter({
131
- filePath,
132
- metadata,
133
- schema: ruleFrontmatterSchema,
134
- });
135
- if (!ruleMetadata) {
136
- continue;
137
- }
138
- const normalized = normalizeRuleMetadata(ruleMetadata);
139
- const copilotInstructionPath = path.join(targetRoots.copilotInstructions, `${fileName}.instructions.md`);
140
- const cursorRulePath = path.join(targetRoots.cursorRules, `${fileName}.mdc`);
141
- const copilotMetadata = compactObject({
142
- description: ruleMetadata.description,
143
- applyTo: normalized.applyTo,
144
- });
145
- const cursorMetadata = compactObject({
146
- description: ruleMetadata.description,
147
- globs: normalized.alwaysApply ? undefined : normalized.globs,
148
- alwaysApply: normalized.alwaysApply,
149
- });
150
- installItems.push({
151
- kind: 'rule',
152
- editors: ALL_INSTALL_EDITORS,
153
- name: fileName,
154
- sourcePath: filePath,
155
- ownershipKeys: [
156
- `copilot-instruction-path:${copilotInstructionPath}`,
157
- `cursor-rule-path:${cursorRulePath}`,
158
- ],
159
- targets: [
160
- { editor: 'copilot', outputPath: copilotInstructionPath },
161
- { editor: 'cursor', outputPath: cursorRulePath },
162
- ],
163
- install: async () => {
164
- await writeMarkdownFile(copilotInstructionPath, copilotMetadata, body);
165
- await writeMarkdownFile(cursorRulePath, cursorMetadata, body);
166
- },
167
- });
168
- }
169
- return installItems;
170
- }
171
- /**
172
- * Collects install operations for local skill directories.
173
- */
174
- async function collectSkillInstallItems(context, { targetRoots }) {
175
- await fs.ensureDir(context.sourceRoots.skills);
176
- const entries = await fs.readdir(context.sourceRoots.skills, {
177
- withFileTypes: true,
178
- });
179
- const installItems = [];
180
- for (const entry of entries) {
181
- if (!entry.isDirectory()) {
182
- continue;
183
- }
184
- const sourceDir = path.join(context.sourceRoots.skills, entry.name);
185
- const copilotSkillPath = path.join(targetRoots.copilotSkills, entry.name);
186
- const cursorSkillPath = path.join(targetRoots.cursorSkills, entry.name);
187
- installItems.push({
188
- kind: 'skill',
189
- editors: ALL_INSTALL_EDITORS,
190
- name: entry.name,
191
- sourcePath: sourceDir,
192
- ownershipKeys: [
193
- `copilot-skill-name:${entry.name}`,
194
- `cursor-skill-name:${entry.name}`,
195
- ],
196
- targets: [
197
- { editor: 'copilot', outputPath: copilotSkillPath },
198
- { editor: 'cursor', outputPath: cursorSkillPath },
199
- ],
200
- install: async () => {
201
- await copyDirectoryContents(sourceDir, copilotSkillPath);
202
- await copyDirectoryContents(sourceDir, cursorSkillPath);
203
- },
204
- });
205
- }
206
- return installItems;
207
- }
208
- /**
209
- * Copies a skill directory into a target directory after clearing any previous contents.
210
- */
211
- async function copyDirectoryContents(sourceDir, targetDir) {
212
- await fs.emptyDir(targetDir);
213
- const entryNames = await fs.readdir(sourceDir);
214
- for (const entryName of entryNames) {
215
- await fs.copy(path.join(sourceDir, entryName), path.join(targetDir, entryName));
216
- }
217
- }
218
- /**
219
- * Collects the installable and skipped items after analyzing output namespace conflicts.
220
- */
221
- function collectConflictFilterResult(items) {
222
- const ownershipMap = new Map();
223
- for (const item of items) {
224
- for (const ownershipKey of item.ownershipKeys) {
225
- const existingOwners = ownershipMap.get(ownershipKey);
226
- if (existingOwners) {
227
- existingOwners.push(item);
228
- }
229
- else {
230
- ownershipMap.set(ownershipKey, [item]);
231
- }
232
- }
233
- }
234
- const skippedItemsBySourcePath = new Map();
235
- for (const [ownershipKey, owners] of ownershipMap) {
236
- if (owners.length < 2) {
237
- continue;
238
- }
239
- const conflictDescription = describeOwnershipKey(ownershipKey);
240
- for (const owner of owners) {
241
- const existingSkippedItem = skippedItemsBySourcePath.get(owner.sourcePath);
242
- if (existingSkippedItem) {
243
- existingSkippedItem.conflictDescriptions.push(conflictDescription);
244
- }
245
- else {
246
- skippedItemsBySourcePath.set(owner.sourcePath, {
247
- item: owner,
248
- conflictDescriptions: [conflictDescription],
249
- });
250
- }
251
- }
252
- }
253
- const skippedItems = [...skippedItemsBySourcePath.values()].map((skippedItem) => ({
254
- item: skippedItem.item,
255
- conflictDescriptions: [
256
- ...new Set(skippedItem.conflictDescriptions),
257
- ].sort(),
258
- }));
259
- const skippedSourcePaths = new Set(skippedItems.map((skippedItem) => skippedItem.item.sourcePath));
260
- return {
261
- installableItems: items.filter((item) => !skippedSourcePaths.has(item.sourcePath)),
262
- skippedItems,
263
- };
264
- }
265
- /**
266
- * Renders an install summary grouped by editor, item kind, and skipped conflicts.
267
- */
268
- function renderInstallReport(appliedItems, skippedItems) {
269
- const sections = [chalk.bold.cyan('Applied changes:')];
270
- const copilotInstalledItems = collectEditorAppliedInstallItems(appliedItems, 'copilot');
271
- const cursorInstalledItems = collectEditorAppliedInstallItems(appliedItems, 'cursor');
272
- sections.push(renderEditorInstallSection('Copilot', copilotInstalledItems));
273
- sections.push(renderEditorInstallSection('Cursor', cursorInstalledItems));
274
- if (skippedItems.length === 0) {
275
- sections.push(`${chalk.bold.green('Skipped conflicts:')} ${chalk.green('None')}`);
276
- }
277
- else {
278
- const skippedLines = skippedItems
279
- .slice()
280
- .sort((left, right) => formatInstallItemLabel(left.item).localeCompare(formatInstallItemLabel(right.item)))
281
- .map((skippedItem) => [
282
- `- ${chalk.red(formatInstallItemLabel(skippedItem.item))}`,
283
- ` * ${chalk.bold.red('due to:')} ${chalk.yellow(skippedItem.conflictDescriptions.join(', '))}`,
284
- ].join('\n'));
285
- sections.push(`${chalk.bold.red('Skipped conflicts:')}\n${skippedLines.join('\n')}`);
286
- }
287
- return sections.join('\n\n');
288
- }
289
- /**
290
- * Collects the applied install items relevant to one editor.
291
- */
292
- function collectEditorAppliedInstallItems(appliedItems, editor) {
293
- return appliedItems.flatMap((appliedItem) => appliedItem.changes
294
- .filter((change) => change.editor === editor)
295
- .map((change) => ({
296
- item: appliedItem.item,
297
- changeType: change.changeType,
298
- })));
299
- }
300
- /**
301
- * Renders the installed items for one editor grouped by item kind.
302
- */
303
- function renderEditorInstallSection(editorLabel, installedItems) {
304
- const kindSections = [
305
- renderKindInstallLine('commands', 'command', installedItems),
306
- renderKindInstallLine('rules', 'rule', installedItems),
307
- renderKindInstallLine('skills', 'skill', installedItems),
308
- ].filter((section) => section !== undefined);
309
- return [`- ${colorEditorLabel(editorLabel)}`, ...kindSections].join('\n');
310
- }
311
- /**
312
- * Renders one install summary section for a specific item kind.
313
- */
314
- function renderKindInstallLine(label, kind, installedItems) {
315
- const matchingItems = installedItems
316
- .filter((item) => item.item.kind === kind)
317
- .slice()
318
- .sort((left, right) => left.item.name.localeCompare(right.item.name));
319
- if (matchingItems.length === 0) {
320
- return undefined;
321
- }
322
- return [
323
- ` * ${colorKindLabel(label)}`,
324
- ...matchingItems.map(renderAppliedInstallItemLine),
325
- ].join('\n');
326
- }
327
- /**
328
- * Returns the styled editor label used in the install summary.
329
- */
330
- function colorEditorLabel(editorLabel) {
331
- return chalk.bold.blue(editorLabel);
332
- }
333
- /**
334
- * Returns the styled item-kind label used in the install summary.
335
- */
336
- function colorKindLabel(label) {
337
- return chalk.bold.yellow(label);
338
- }
339
- /**
340
- * Returns the styled change-type label used in the install summary.
341
- */
342
- function colorChangeType(changeType) {
343
- if (changeType === 'install') {
344
- return chalk.green(changeType);
345
- }
346
- return chalk.yellow(changeType);
347
- }
348
- /**
349
- * Renders one styled applied-item line in the install summary.
350
- */
351
- function renderAppliedInstallItemLine(matchingItem) {
352
- return ` - ${chalk.whiteBright(matchingItem.item.name)} (${colorChangeType(matchingItem.changeType)})`;
353
- }
354
- /**
355
- * Returns a readable label for one install item in conflict warnings.
356
- */
357
- function formatInstallItemLabel(item) {
358
- return `${item.kind} "${item.name}" from ${item.sourcePath}`;
359
- }
360
- /**
361
- * Converts an internal ownership key into a warning message fragment.
362
- */
363
- function describeOwnershipKey(ownershipKey) {
364
- if (ownershipKey.startsWith('cursor-skill-name:')) {
365
- return `Cursor skill name "${ownershipKey.slice('cursor-skill-name:'.length)}"`;
366
- }
367
- if (ownershipKey.startsWith('copilot-skill-name:')) {
368
- return `Copilot skill name "${ownershipKey.slice('copilot-skill-name:'.length)}"`;
369
- }
370
- if (ownershipKey.startsWith('copilot-prompt-path:')) {
371
- return `Copilot prompt output "${ownershipKey.slice('copilot-prompt-path:'.length)}"`;
372
- }
373
- if (ownershipKey.startsWith('copilot-instruction-path:')) {
374
- return `Copilot instruction output "${ownershipKey.slice('copilot-instruction-path:'.length)}"`;
375
- }
376
- if (ownershipKey.startsWith('cursor-rule-path:')) {
377
- return `Cursor rule output "${ownershipKey.slice('cursor-rule-path:'.length)}"`;
378
- }
379
- return `output namespace "${ownershipKey}"`;
380
- }