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,402 @@
1
+ /**
2
+ * Package Registry Tests
3
+ *
4
+ * Tests for package.busy.md parsing and manipulation.
5
+ * TDD approach for package manager implementation.
6
+ */
7
+
8
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
9
+ import { promises as fs } from 'node:fs';
10
+ import * as path from 'node:path';
11
+ import * as os from 'node:os';
12
+
13
+ // We'll implement these after tests
14
+ import {
15
+ PackageRegistry,
16
+ PackageEntry,
17
+ parsePackageRegistry,
18
+ deriveEntryId,
19
+ deriveCategory,
20
+ } from '../registry/index.js';
21
+
22
+ const SAMPLE_PACKAGE_BUSY_MD = `---
23
+ Name: package
24
+ Type: Document
25
+ Description: Package registry for this workspace
26
+ ---
27
+
28
+ # Local Definitions
29
+
30
+ ## Dependency
31
+
32
+ An installed BUSY package.
33
+
34
+ | Field | Required | Description |
35
+ |-------|----------|-------------|
36
+ | Source | Yes | URL to package.busy.md or file |
37
+ | Provider | Yes | github, gitlab, url |
38
+ | Cached | Yes | Local path in .libraries/ |
39
+ | Version | Yes | Semantic version or tag |
40
+ | Fetched | Yes | ISO 8601 timestamp |
41
+ | Integrity | No | sha256:{hash} |
42
+
43
+ ---
44
+
45
+ # Dependencies
46
+
47
+ ## Core Library
48
+
49
+ ### executeprompt
50
+
51
+ Execute a prompt operation.
52
+
53
+ | Field | Value |
54
+ |-------|-------|
55
+ | Source | https://github.com/Bravo-Tensor/busy-lang/blob/v0.3.1/busy-v2/core/prompt.md#executeprompt |
56
+ | Provider | github |
57
+ | Cached | .libraries/busy-lang/busy-v2/core/prompt.md |
58
+ | Version | v0.3.1 |
59
+ | Fetched | 2026-01-21T10:30:00Z |
60
+ | Integrity | sha256:abc123def456 |
61
+
62
+ [Source][executeprompt_src] | [Local][executeprompt_local]
63
+
64
+ [executeprompt_src]: https://github.com/Bravo-Tensor/busy-lang/blob/v0.3.1/busy-v2/core/prompt.md#executeprompt
65
+ [executeprompt_local]: .libraries/busy-lang/busy-v2/core/prompt.md
66
+
67
+ ## Tools
68
+
69
+ ### emit-event
70
+
71
+ Emit an event to the runtime.
72
+
73
+ | Field | Value |
74
+ |-------|-------|
75
+ | Source | https://github.com/Bravo-Tensor/busy-lang/blob/v0.3.1/busy-v2/tools/emit-event.busy.md |
76
+ | Provider | github |
77
+ | Cached | .libraries/busy-lang/busy-v2/tools/emit-event.busy.md |
78
+ | Version | v0.3.1 |
79
+ | Fetched | 2026-01-21T10:30:00Z |
80
+ `;
81
+
82
+ describe('parsePackageRegistry', () => {
83
+ it('should parse frontmatter', () => {
84
+ const registry = parsePackageRegistry(SAMPLE_PACKAGE_BUSY_MD);
85
+
86
+ expect(registry.metadata.name).toBe('package');
87
+ expect(registry.metadata.type).toBe('Document');
88
+ expect(registry.metadata.description).toBe('Package registry for this workspace');
89
+ });
90
+
91
+ it('should parse package entries', () => {
92
+ const registry = parsePackageRegistry(SAMPLE_PACKAGE_BUSY_MD);
93
+
94
+ expect(registry.packages).toHaveLength(2);
95
+
96
+ const executeprompt = registry.packages.find(p => p.id === 'executeprompt');
97
+ expect(executeprompt).toBeDefined();
98
+ expect(executeprompt?.source).toBe('https://github.com/Bravo-Tensor/busy-lang/blob/v0.3.1/busy-v2/core/prompt.md#executeprompt');
99
+ expect(executeprompt?.provider).toBe('github');
100
+ expect(executeprompt?.cached).toBe('.libraries/busy-lang/busy-v2/core/prompt.md');
101
+ expect(executeprompt?.version).toBe('v0.3.1');
102
+ expect(executeprompt?.fetched).toBe('2026-01-21T10:30:00Z');
103
+ expect(executeprompt?.integrity).toBe('sha256:abc123def456');
104
+ expect(executeprompt?.category).toBe('Core Library');
105
+ expect(executeprompt?.description).toBe('Execute a prompt operation.');
106
+ });
107
+
108
+ it('should parse entries without optional fields', () => {
109
+ const registry = parsePackageRegistry(SAMPLE_PACKAGE_BUSY_MD);
110
+
111
+ const emitEvent = registry.packages.find(p => p.id === 'emit-event');
112
+ expect(emitEvent).toBeDefined();
113
+ expect(emitEvent?.integrity).toBeUndefined();
114
+ });
115
+
116
+ it('should handle empty registry', () => {
117
+ const emptyRegistry = `---
118
+ Name: package
119
+ Type: Document
120
+ Description: Empty package registry
121
+ ---
122
+
123
+ # Local Definitions
124
+
125
+ ## Dependency
126
+
127
+ | Field | Required | Description |
128
+ |-------|----------|-------------|
129
+ | Source | Yes | URL |
130
+
131
+ ---
132
+
133
+ # Dependencies
134
+
135
+ No dependencies installed.
136
+ `;
137
+
138
+ const registry = parsePackageRegistry(emptyRegistry);
139
+
140
+ expect(registry.packages).toHaveLength(0);
141
+ });
142
+ });
143
+
144
+ describe('PackageRegistry', () => {
145
+ let tempDir: string;
146
+ let registry: PackageRegistry;
147
+
148
+ beforeEach(async () => {
149
+ tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'busy-registry-test-'));
150
+ registry = new PackageRegistry(tempDir);
151
+ });
152
+
153
+ afterEach(async () => {
154
+ await fs.rm(tempDir, { recursive: true, force: true });
155
+ });
156
+
157
+ describe('init', () => {
158
+ it('should create package.busy.md with defaults', async () => {
159
+ await registry.init();
160
+
161
+ const packagePath = path.join(tempDir, 'package.busy.md');
162
+ const exists = await fs.stat(packagePath).then(() => true).catch(() => false);
163
+ expect(exists).toBe(true);
164
+ });
165
+
166
+ it('should not overwrite existing package.busy.md', async () => {
167
+ const packagePath = path.join(tempDir, 'package.busy.md');
168
+ await fs.writeFile(packagePath, 'existing content');
169
+
170
+ await registry.init();
171
+
172
+ const content = await fs.readFile(packagePath, 'utf-8');
173
+ expect(content).toBe('existing content');
174
+ });
175
+ });
176
+
177
+ describe('load', () => {
178
+ it('should load existing package.busy.md', async () => {
179
+ const packagePath = path.join(tempDir, 'package.busy.md');
180
+ await fs.writeFile(packagePath, SAMPLE_PACKAGE_BUSY_MD);
181
+
182
+ await registry.load();
183
+
184
+ const packages = registry.getPackages();
185
+ expect(packages).toHaveLength(2);
186
+ });
187
+
188
+ it('should throw if package.busy.md does not exist', async () => {
189
+ await expect(registry.load()).rejects.toThrow();
190
+ });
191
+ });
192
+
193
+ describe('getPackage', () => {
194
+ beforeEach(async () => {
195
+ const packagePath = path.join(tempDir, 'package.busy.md');
196
+ await fs.writeFile(packagePath, SAMPLE_PACKAGE_BUSY_MD);
197
+ await registry.load();
198
+ });
199
+
200
+ it('should get package by id', () => {
201
+ const pkg = registry.getPackage('executeprompt');
202
+
203
+ expect(pkg).toBeDefined();
204
+ expect(pkg?.source).toContain('prompt.md#executeprompt');
205
+ });
206
+
207
+ it('should return undefined for non-existent package', () => {
208
+ const pkg = registry.getPackage('nonexistent');
209
+ expect(pkg).toBeUndefined();
210
+ });
211
+ });
212
+
213
+ describe('addPackage', () => {
214
+ beforeEach(async () => {
215
+ await registry.init();
216
+ await registry.load();
217
+ });
218
+
219
+ it('should add a new package', () => {
220
+ const entry: PackageEntry = {
221
+ id: 'new-package',
222
+ description: 'A new package',
223
+ source: 'https://github.com/org/repo/blob/main/file.md',
224
+ provider: 'github',
225
+ cached: '.libraries/repo/file.md',
226
+ version: 'v1.0.0',
227
+ fetched: new Date().toISOString(),
228
+ category: 'Packages',
229
+ };
230
+
231
+ registry.addPackage(entry);
232
+
233
+ const pkg = registry.getPackage('new-package');
234
+ expect(pkg).toEqual(entry);
235
+ });
236
+
237
+ it('should update existing package', () => {
238
+ const entry: PackageEntry = {
239
+ id: 'test-package',
240
+ description: 'Test',
241
+ source: 'https://example.com/file.md',
242
+ provider: 'url',
243
+ cached: '.libraries/file.md',
244
+ version: 'v1.0.0',
245
+ fetched: new Date().toISOString(),
246
+ category: 'Packages',
247
+ };
248
+
249
+ registry.addPackage(entry);
250
+
251
+ const updatedEntry = { ...entry, version: 'v2.0.0' };
252
+ registry.addPackage(updatedEntry);
253
+
254
+ const pkg = registry.getPackage('test-package');
255
+ expect(pkg?.version).toBe('v2.0.0');
256
+ });
257
+ });
258
+
259
+ describe('removePackage', () => {
260
+ beforeEach(async () => {
261
+ const packagePath = path.join(tempDir, 'package.busy.md');
262
+ await fs.writeFile(packagePath, SAMPLE_PACKAGE_BUSY_MD);
263
+ await registry.load();
264
+ });
265
+
266
+ it('should remove package by id', () => {
267
+ const removed = registry.removePackage('executeprompt');
268
+
269
+ expect(removed).toBe(true);
270
+ expect(registry.getPackage('executeprompt')).toBeUndefined();
271
+ });
272
+
273
+ it('should return false for non-existent package', () => {
274
+ const removed = registry.removePackage('nonexistent');
275
+ expect(removed).toBe(false);
276
+ });
277
+ });
278
+
279
+ describe('save', () => {
280
+ beforeEach(async () => {
281
+ await registry.init();
282
+ await registry.load();
283
+ });
284
+
285
+ it('should save changes to package.busy.md', async () => {
286
+ const entry: PackageEntry = {
287
+ id: 'new-package',
288
+ description: 'A new package',
289
+ source: 'https://github.com/org/repo/blob/main/file.md',
290
+ provider: 'github',
291
+ cached: '.libraries/repo/file.md',
292
+ version: 'v1.0.0',
293
+ fetched: '2026-01-21T10:00:00Z',
294
+ category: 'Packages',
295
+ };
296
+
297
+ registry.addPackage(entry);
298
+ await registry.save();
299
+
300
+ // Reload and verify
301
+ const newRegistry = new PackageRegistry(tempDir);
302
+ await newRegistry.load();
303
+
304
+ const pkg = newRegistry.getPackage('new-package');
305
+ expect(pkg).toBeDefined();
306
+ expect(pkg?.version).toBe('v1.0.0');
307
+ });
308
+ });
309
+
310
+ describe('getPackagesByCategory', () => {
311
+ beforeEach(async () => {
312
+ const packagePath = path.join(tempDir, 'package.busy.md');
313
+ await fs.writeFile(packagePath, SAMPLE_PACKAGE_BUSY_MD);
314
+ await registry.load();
315
+ });
316
+
317
+ it('should return packages in category', () => {
318
+ const corePackages = registry.getPackagesByCategory('Core Library');
319
+ expect(corePackages).toHaveLength(1);
320
+ expect(corePackages[0].id).toBe('executeprompt');
321
+ });
322
+
323
+ it('should return empty array for non-existent category', () => {
324
+ const packages = registry.getPackagesByCategory('Nonexistent');
325
+ expect(packages).toHaveLength(0);
326
+ });
327
+ });
328
+
329
+ describe('getCategories', () => {
330
+ beforeEach(async () => {
331
+ const packagePath = path.join(tempDir, 'package.busy.md');
332
+ await fs.writeFile(packagePath, SAMPLE_PACKAGE_BUSY_MD);
333
+ await registry.load();
334
+ });
335
+
336
+ it('should return all categories', () => {
337
+ const categories = registry.getCategories();
338
+
339
+ expect(categories).toContain('Core Library');
340
+ expect(categories).toContain('Tools');
341
+ });
342
+ });
343
+ });
344
+
345
+ describe('deriveEntryId', () => {
346
+ it('should use anchor if present', () => {
347
+ const url = 'https://github.com/org/repo/blob/main/file.md#executeprompt';
348
+ expect(deriveEntryId(url)).toBe('executeprompt');
349
+ });
350
+
351
+ it('should use filename without extension if no anchor', () => {
352
+ const url = 'https://github.com/org/repo/blob/main/emit-event.busy.md';
353
+ expect(deriveEntryId(url)).toBe('emit-event');
354
+ });
355
+
356
+ it('should handle .md extension', () => {
357
+ const url = 'https://github.com/org/repo/blob/main/prompt.md';
358
+ expect(deriveEntryId(url)).toBe('prompt');
359
+ });
360
+
361
+ it('should handle nested paths', () => {
362
+ const url = 'https://github.com/org/repo/blob/main/tools/emit-event.busy.md';
363
+ expect(deriveEntryId(url)).toBe('emit-event');
364
+ });
365
+
366
+ it('should slugify the result', () => {
367
+ const url = 'https://github.com/org/repo/blob/main/My File.md';
368
+ expect(deriveEntryId(url)).toBe('my-file');
369
+ });
370
+
371
+ it('should handle uppercase anchors', () => {
372
+ const url = 'https://github.com/org/repo/blob/main/file.md#ExecutePrompt';
373
+ expect(deriveEntryId(url)).toBe('executeprompt');
374
+ });
375
+ });
376
+
377
+ describe('deriveCategory', () => {
378
+ it('should return Core Library for /core/ path', () => {
379
+ const url = 'https://github.com/org/repo/blob/main/core/prompt.md';
380
+ expect(deriveCategory(url)).toBe('Core Library');
381
+ });
382
+
383
+ it('should return Tools for /tools/ path', () => {
384
+ const url = 'https://github.com/org/repo/blob/main/tools/emit.md';
385
+ expect(deriveCategory(url)).toBe('Tools');
386
+ });
387
+
388
+ it('should return Concepts for /concepts/ path', () => {
389
+ const url = 'https://github.com/org/repo/blob/main/concepts/operation.md';
390
+ expect(deriveCategory(url)).toBe('Concepts');
391
+ });
392
+
393
+ it('should return Packages as default', () => {
394
+ const url = 'https://github.com/org/repo/blob/main/other/file.md';
395
+ expect(deriveCategory(url)).toBe('Packages');
396
+ });
397
+
398
+ it('should handle case insensitively', () => {
399
+ const url = 'https://github.com/org/repo/blob/main/CORE/prompt.md';
400
+ expect(deriveCategory(url)).toBe('Core Library');
401
+ });
402
+ });