busy-cli 0.1.2

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 (128) hide show
  1. package/README.md +129 -0
  2. package/dist/builders/context.d.ts +50 -0
  3. package/dist/builders/context.d.ts.map +1 -0
  4. package/dist/builders/context.js +190 -0
  5. package/dist/cache/index.d.ts +100 -0
  6. package/dist/cache/index.d.ts.map +1 -0
  7. package/dist/cache/index.js +270 -0
  8. package/dist/cli/index.d.ts +3 -0
  9. package/dist/cli/index.d.ts.map +1 -0
  10. package/dist/cli/index.js +463 -0
  11. package/dist/commands/package.d.ts +96 -0
  12. package/dist/commands/package.d.ts.map +1 -0
  13. package/dist/commands/package.js +285 -0
  14. package/dist/index.d.ts +7 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +7 -0
  17. package/dist/loader.d.ts +6 -0
  18. package/dist/loader.d.ts.map +1 -0
  19. package/dist/loader.js +361 -0
  20. package/dist/merge.d.ts +16 -0
  21. package/dist/merge.d.ts.map +1 -0
  22. package/dist/merge.js +102 -0
  23. package/dist/package/manifest.d.ts +59 -0
  24. package/dist/package/manifest.d.ts.map +1 -0
  25. package/dist/package/manifest.js +265 -0
  26. package/dist/parser.d.ts +28 -0
  27. package/dist/parser.d.ts.map +1 -0
  28. package/dist/parser.js +220 -0
  29. package/dist/parsers/frontmatter.d.ts +14 -0
  30. package/dist/parsers/frontmatter.d.ts.map +1 -0
  31. package/dist/parsers/frontmatter.js +110 -0
  32. package/dist/parsers/imports.d.ts +48 -0
  33. package/dist/parsers/imports.d.ts.map +1 -0
  34. package/dist/parsers/imports.js +147 -0
  35. package/dist/parsers/links.d.ts +12 -0
  36. package/dist/parsers/links.d.ts.map +1 -0
  37. package/dist/parsers/links.js +79 -0
  38. package/dist/parsers/localdefs.d.ts +6 -0
  39. package/dist/parsers/localdefs.d.ts.map +1 -0
  40. package/dist/parsers/localdefs.js +132 -0
  41. package/dist/parsers/operations.d.ts +32 -0
  42. package/dist/parsers/operations.d.ts.map +1 -0
  43. package/dist/parsers/operations.js +313 -0
  44. package/dist/parsers/sections.d.ts +15 -0
  45. package/dist/parsers/sections.d.ts.map +1 -0
  46. package/dist/parsers/sections.js +173 -0
  47. package/dist/parsers/tools.d.ts +30 -0
  48. package/dist/parsers/tools.d.ts.map +1 -0
  49. package/dist/parsers/tools.js +178 -0
  50. package/dist/parsers/triggers.d.ts +35 -0
  51. package/dist/parsers/triggers.d.ts.map +1 -0
  52. package/dist/parsers/triggers.js +219 -0
  53. package/dist/providers/base.d.ts +60 -0
  54. package/dist/providers/base.d.ts.map +1 -0
  55. package/dist/providers/base.js +34 -0
  56. package/dist/providers/github.d.ts +18 -0
  57. package/dist/providers/github.d.ts.map +1 -0
  58. package/dist/providers/github.js +109 -0
  59. package/dist/providers/gitlab.d.ts +18 -0
  60. package/dist/providers/gitlab.d.ts.map +1 -0
  61. package/dist/providers/gitlab.js +101 -0
  62. package/dist/providers/index.d.ts +13 -0
  63. package/dist/providers/index.d.ts.map +1 -0
  64. package/dist/providers/index.js +17 -0
  65. package/dist/providers/local.d.ts +31 -0
  66. package/dist/providers/local.d.ts.map +1 -0
  67. package/dist/providers/local.js +116 -0
  68. package/dist/providers/url.d.ts +16 -0
  69. package/dist/providers/url.d.ts.map +1 -0
  70. package/dist/providers/url.js +45 -0
  71. package/dist/registry/index.d.ts +99 -0
  72. package/dist/registry/index.d.ts.map +1 -0
  73. package/dist/registry/index.js +320 -0
  74. package/dist/types/schema.d.ts +3259 -0
  75. package/dist/types/schema.d.ts.map +1 -0
  76. package/dist/types/schema.js +258 -0
  77. package/dist/utils/logger.d.ts +19 -0
  78. package/dist/utils/logger.d.ts.map +1 -0
  79. package/dist/utils/logger.js +23 -0
  80. package/dist/utils/slugify.d.ts +14 -0
  81. package/dist/utils/slugify.d.ts.map +1 -0
  82. package/dist/utils/slugify.js +28 -0
  83. package/package.json +61 -0
  84. package/src/__tests__/cache.test.ts +393 -0
  85. package/src/__tests__/cli-package.test.ts +667 -0
  86. package/src/__tests__/fixtures/automated-workflow.busy.md +84 -0
  87. package/src/__tests__/fixtures/concept.busy.md +30 -0
  88. package/src/__tests__/fixtures/document.busy.md +44 -0
  89. package/src/__tests__/fixtures/simple-operation.busy.md +45 -0
  90. package/src/__tests__/fixtures/tool-document.busy.md +71 -0
  91. package/src/__tests__/fixtures/tool.busy.md +54 -0
  92. package/src/__tests__/imports.test.ts +244 -0
  93. package/src/__tests__/integration.test.ts +432 -0
  94. package/src/__tests__/operations.test.ts +408 -0
  95. package/src/__tests__/package-manifest.test.ts +455 -0
  96. package/src/__tests__/providers.test.ts +672 -0
  97. package/src/__tests__/registry.test.ts +402 -0
  98. package/src/__tests__/schema.test.ts +467 -0
  99. package/src/__tests__/tools.test.ts +376 -0
  100. package/src/__tests__/triggers.test.ts +312 -0
  101. package/src/builders/context.ts +294 -0
  102. package/src/cache/index.ts +312 -0
  103. package/src/cli/index.ts +514 -0
  104. package/src/commands/package.ts +392 -0
  105. package/src/index.ts +46 -0
  106. package/src/loader.ts +474 -0
  107. package/src/merge.ts +126 -0
  108. package/src/package/manifest.ts +349 -0
  109. package/src/parser.ts +278 -0
  110. package/src/parsers/frontmatter.ts +135 -0
  111. package/src/parsers/imports.ts +196 -0
  112. package/src/parsers/links.ts +108 -0
  113. package/src/parsers/localdefs.ts +166 -0
  114. package/src/parsers/operations.ts +404 -0
  115. package/src/parsers/sections.ts +230 -0
  116. package/src/parsers/tools.ts +215 -0
  117. package/src/parsers/triggers.ts +252 -0
  118. package/src/providers/base.ts +77 -0
  119. package/src/providers/github.ts +129 -0
  120. package/src/providers/gitlab.ts +121 -0
  121. package/src/providers/index.ts +25 -0
  122. package/src/providers/local.ts +129 -0
  123. package/src/providers/url.ts +56 -0
  124. package/src/registry/index.ts +408 -0
  125. package/src/types/schema.ts +369 -0
  126. package/src/utils/logger.ts +25 -0
  127. package/src/utils/slugify.ts +31 -0
  128. package/tsconfig.json +21 -0
@@ -0,0 +1,408 @@
1
+ /**
2
+ * Package Registry for package.busy.md
3
+ *
4
+ * Parses and manages the package registry file.
5
+ */
6
+
7
+ import { promises as fs } from 'node:fs';
8
+ import * as path from 'node:path';
9
+ import matter from 'gray-matter';
10
+
11
+ /**
12
+ * Package entry in the registry
13
+ */
14
+ export interface PackageEntry {
15
+ id: string;
16
+ description: string;
17
+ source: string;
18
+ provider: string;
19
+ cached: string;
20
+ version: string;
21
+ fetched: string;
22
+ integrity?: string;
23
+ category: string;
24
+ }
25
+
26
+ /**
27
+ * Registry metadata from frontmatter
28
+ */
29
+ export interface RegistryMetadata {
30
+ name: string;
31
+ type: string;
32
+ description: string;
33
+ }
34
+
35
+ /**
36
+ * Parsed package registry
37
+ */
38
+ export interface ParsedRegistry {
39
+ metadata: RegistryMetadata;
40
+ packages: PackageEntry[];
41
+ }
42
+
43
+ /**
44
+ * Derive entry ID from URL
45
+ *
46
+ * If URL has #anchor, use anchor as ID
47
+ * Else, use filename without extension
48
+ * Slugify result (lowercase, hyphens)
49
+ */
50
+ export function deriveEntryId(url: string): string {
51
+ // Check for anchor
52
+ const hashIndex = url.indexOf('#');
53
+ if (hashIndex !== -1) {
54
+ const anchor = url.slice(hashIndex + 1);
55
+ return slugify(anchor);
56
+ }
57
+
58
+ // Use filename without extension
59
+ const urlPath = url.split('?')[0]; // Remove query params
60
+ const filename = path.basename(urlPath);
61
+ const nameWithoutExt = filename.replace(/\.busy\.md$/, '').replace(/\.md$/, '');
62
+ return slugify(nameWithoutExt);
63
+ }
64
+
65
+ /**
66
+ * Derive category from URL path
67
+ */
68
+ export function deriveCategory(url: string): string {
69
+ const lowerUrl = url.toLowerCase();
70
+
71
+ if (lowerUrl.includes('/core/')) {
72
+ return 'Core Library';
73
+ }
74
+ if (lowerUrl.includes('/tools/')) {
75
+ return 'Tools';
76
+ }
77
+ if (lowerUrl.includes('/concepts/')) {
78
+ return 'Concepts';
79
+ }
80
+
81
+ return 'Packages';
82
+ }
83
+
84
+ /**
85
+ * Slugify a string (lowercase, replace spaces with hyphens)
86
+ */
87
+ function slugify(str: string): string {
88
+ return str
89
+ .toLowerCase()
90
+ .replace(/\s+/g, '-')
91
+ .replace(/[^a-z0-9-]/g, '')
92
+ .replace(/-+/g, '-')
93
+ .replace(/^-|-$/g, '');
94
+ }
95
+
96
+ /**
97
+ * Parse a field/value table
98
+ */
99
+ function parseFieldTable(content: string): Record<string, string> {
100
+ const fields: Record<string, string> = {};
101
+
102
+ // Match table rows: | Field | Value |
103
+ const rowPattern = /\|\s*([^|]+?)\s*\|\s*([^|]+?)\s*\|/g;
104
+ let match;
105
+
106
+ while ((match = rowPattern.exec(content)) !== null) {
107
+ const field = match[1].trim();
108
+ const value = match[2].trim();
109
+
110
+ // Skip header row
111
+ if (field === 'Field' || field.startsWith('-')) {
112
+ continue;
113
+ }
114
+
115
+ fields[field] = value;
116
+ }
117
+
118
+ return fields;
119
+ }
120
+
121
+ /**
122
+ * Parse package.busy.md content
123
+ */
124
+ export function parsePackageRegistry(content: string): ParsedRegistry {
125
+ // Extract only first frontmatter block to avoid "multiple documents" error
126
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
127
+ let frontmatter: Record<string, any> = {};
128
+ let body = content;
129
+ if (frontmatterMatch) {
130
+ const { data } = matter(frontmatterMatch[0]);
131
+ frontmatter = data;
132
+ body = content.slice(frontmatterMatch[0].length);
133
+ }
134
+
135
+ const metadata: RegistryMetadata = {
136
+ name: frontmatter.Name || frontmatter.name || 'package',
137
+ type: frontmatter.Type || frontmatter.type || 'Document',
138
+ description: frontmatter.Description || frontmatter.description || '',
139
+ };
140
+
141
+ const packages: PackageEntry[] = [];
142
+
143
+ // Find Dependencies section (or legacy Package Registry section)
144
+ const registryMatch = body.match(/# (?:Dependencies|Package Registry)\n([\s\S]*?)(?=\n# |$)/);
145
+ if (!registryMatch) {
146
+ return { metadata, packages };
147
+ }
148
+
149
+ const registryContent = registryMatch[1];
150
+
151
+ // Parse entries directly under Dependencies (H2 headers) or in categories (H3 headers)
152
+ // Try H2 entries first (new format: ### package-name under # Dependencies)
153
+ const h2EntryPattern = /## ([^\n]+)\n([\s\S]*?)(?=\n## |$)/g;
154
+ let h2Match;
155
+
156
+ while ((h2Match = h2EntryPattern.exec(registryContent)) !== null) {
157
+ const entryId = h2Match[1].trim();
158
+ const entryContent = h2Match[2];
159
+
160
+ // Check if this is a category with H3 entries or a direct package entry
161
+ if (entryContent.includes('### ')) {
162
+ // This is a category, parse H3 entries
163
+ const category = entryId;
164
+ const entryPattern = /### ([^\n]+)\n([\s\S]*?)(?=\n### |$)/g;
165
+ let entryMatch;
166
+
167
+ while ((entryMatch = entryPattern.exec(entryContent)) !== null) {
168
+ const pkgId = entryMatch[1].trim();
169
+ const pkgContent = entryMatch[2];
170
+
171
+ const descMatch = pkgContent.match(/^([\s\S]*?)(?=\n\|)/);
172
+ const description = descMatch ? descMatch[1].trim() : '';
173
+
174
+ const fields = parseFieldTable(pkgContent);
175
+
176
+ if (fields['Source']) {
177
+ packages.push({
178
+ id: pkgId,
179
+ description,
180
+ source: fields['Source'],
181
+ provider: fields['Provider'] || 'url',
182
+ cached: fields['Cached'] || '',
183
+ version: fields['Version'] || '',
184
+ fetched: fields['Fetched'] || '',
185
+ integrity: fields['Integrity'] || undefined,
186
+ category,
187
+ });
188
+ }
189
+ }
190
+ } else {
191
+ // This is a direct package entry (no category)
192
+ const descMatch = entryContent.match(/^([\s\S]*?)(?=\n\|)/);
193
+ const description = descMatch ? descMatch[1].trim() : '';
194
+
195
+ const fields = parseFieldTable(entryContent);
196
+
197
+ if (fields['Source']) {
198
+ packages.push({
199
+ id: entryId,
200
+ description,
201
+ source: fields['Source'],
202
+ provider: fields['Provider'] || 'url',
203
+ cached: fields['Cached'] || '',
204
+ version: fields['Version'] || '',
205
+ fetched: fields['Fetched'] || '',
206
+ integrity: fields['Integrity'] || undefined,
207
+ category: 'Packages',
208
+ });
209
+ }
210
+ }
211
+ }
212
+
213
+ return { metadata, packages };
214
+ }
215
+
216
+ /**
217
+ * Generate package.busy.md content
218
+ */
219
+ function generateRegistryContent(
220
+ metadata: RegistryMetadata,
221
+ packages: PackageEntry[]
222
+ ): string {
223
+ const lines: string[] = [];
224
+
225
+ // Frontmatter
226
+ lines.push('---');
227
+ lines.push(`Name: ${metadata.name}`);
228
+ lines.push(`Type: ${metadata.type}`);
229
+ lines.push(`Description: ${metadata.description}`);
230
+ lines.push('---');
231
+ lines.push('');
232
+
233
+ // Imports section
234
+ lines.push('# [Imports]');
235
+ lines.push('');
236
+
237
+ // Package Contents section
238
+ lines.push('# Package Contents');
239
+ lines.push('');
240
+ lines.push('No local documents yet.');
241
+ lines.push('');
242
+
243
+ // Dependencies section
244
+ lines.push('# Dependencies');
245
+ lines.push('');
246
+
247
+ if (packages.length === 0) {
248
+ lines.push('No dependencies installed.');
249
+ lines.push('');
250
+ return lines.join('\n');
251
+ }
252
+
253
+ // Group packages by category
254
+ const categories = new Map<string, PackageEntry[]>();
255
+
256
+ for (const pkg of packages) {
257
+ if (!categories.has(pkg.category)) {
258
+ categories.set(pkg.category, []);
259
+ }
260
+ categories.get(pkg.category)!.push(pkg);
261
+ }
262
+
263
+ // Render each category
264
+ for (const [category, pkgs] of categories) {
265
+ lines.push(`## ${category}`);
266
+ lines.push('');
267
+
268
+ for (const pkg of pkgs) {
269
+ lines.push(`### ${pkg.id}`);
270
+ lines.push('');
271
+ if (pkg.description) {
272
+ lines.push(pkg.description);
273
+ lines.push('');
274
+ }
275
+ lines.push('| Field | Value |');
276
+ lines.push('|-------|-------|');
277
+ lines.push(`| Source | ${pkg.source} |`);
278
+ lines.push(`| Provider | ${pkg.provider} |`);
279
+ lines.push(`| Cached | ${pkg.cached} |`);
280
+ lines.push(`| Version | ${pkg.version} |`);
281
+ lines.push(`| Fetched | ${pkg.fetched} |`);
282
+ if (pkg.integrity) {
283
+ lines.push(`| Integrity | ${pkg.integrity} |`);
284
+ }
285
+ lines.push('');
286
+ }
287
+ }
288
+
289
+ return lines.join('\n');
290
+ }
291
+
292
+ const DEFAULT_REGISTRY_CONTENT = `---
293
+ Name: workspace
294
+ Type: [Package]
295
+ Description: BUSY workspace package manifest
296
+ ---
297
+
298
+ # [Imports]
299
+
300
+ # Package Contents
301
+
302
+ No local documents yet.
303
+
304
+ # Dependencies
305
+
306
+ No dependencies installed.
307
+ `;
308
+
309
+ /**
310
+ * Package Registry Manager
311
+ *
312
+ * Manages the package.busy.md file in a workspace.
313
+ */
314
+ export class PackageRegistry {
315
+ private workspaceRoot: string;
316
+ private registryPath: string;
317
+ private metadata: RegistryMetadata;
318
+ private packages: Map<string, PackageEntry>;
319
+
320
+ constructor(workspaceRoot: string) {
321
+ this.workspaceRoot = workspaceRoot;
322
+ this.registryPath = path.join(workspaceRoot, 'package.busy.md');
323
+ this.metadata = {
324
+ name: 'workspace',
325
+ type: '[Package]',
326
+ description: 'BUSY workspace package manifest',
327
+ };
328
+ this.packages = new Map();
329
+ }
330
+
331
+ /**
332
+ * Initialize a new package.busy.md if it doesn't exist
333
+ */
334
+ async init(): Promise<void> {
335
+ const exists = await fs.stat(this.registryPath).then(() => true).catch(() => false);
336
+ if (!exists) {
337
+ await fs.writeFile(this.registryPath, DEFAULT_REGISTRY_CONTENT, 'utf-8');
338
+ }
339
+ }
340
+
341
+ /**
342
+ * Load existing package.busy.md
343
+ */
344
+ async load(): Promise<void> {
345
+ const content = await fs.readFile(this.registryPath, 'utf-8');
346
+ const parsed = parsePackageRegistry(content);
347
+
348
+ this.metadata = parsed.metadata;
349
+ this.packages = new Map(parsed.packages.map(p => [p.id, p]));
350
+ }
351
+
352
+ /**
353
+ * Save changes to package.busy.md
354
+ */
355
+ async save(): Promise<void> {
356
+ const content = generateRegistryContent(
357
+ this.metadata,
358
+ Array.from(this.packages.values())
359
+ );
360
+ await fs.writeFile(this.registryPath, content, 'utf-8');
361
+ }
362
+
363
+ /**
364
+ * Get all packages
365
+ */
366
+ getPackages(): PackageEntry[] {
367
+ return Array.from(this.packages.values());
368
+ }
369
+
370
+ /**
371
+ * Get package by ID
372
+ */
373
+ getPackage(id: string): PackageEntry | undefined {
374
+ return this.packages.get(id);
375
+ }
376
+
377
+ /**
378
+ * Add or update a package
379
+ */
380
+ addPackage(entry: PackageEntry): void {
381
+ this.packages.set(entry.id, entry);
382
+ }
383
+
384
+ /**
385
+ * Remove a package by ID
386
+ */
387
+ removePackage(id: string): boolean {
388
+ return this.packages.delete(id);
389
+ }
390
+
391
+ /**
392
+ * Get packages by category
393
+ */
394
+ getPackagesByCategory(category: string): PackageEntry[] {
395
+ return Array.from(this.packages.values()).filter(p => p.category === category);
396
+ }
397
+
398
+ /**
399
+ * Get all unique categories
400
+ */
401
+ getCategories(): string[] {
402
+ const categories = new Set<string>();
403
+ for (const pkg of this.packages.values()) {
404
+ categories.add(pkg.category);
405
+ }
406
+ return Array.from(categories);
407
+ }
408
+ }