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,467 @@
1
+ /**
2
+ * Schema Tests - Define expected data models matching busy-python
3
+ *
4
+ * These tests define the API contract for BUSY document parsing.
5
+ * The schemas should match busy-python's Pydantic models as the source of truth.
6
+ */
7
+
8
+ import { describe, it, expect } from 'vitest';
9
+ import { z } from 'zod';
10
+
11
+ // Import schemas - use New* schemas for busy-python compatible types
12
+ import {
13
+ MetadataSchema,
14
+ ImportSchema,
15
+ LocalDefinitionSchema,
16
+ StepSchema,
17
+ ChecklistSchema,
18
+ TriggerSchema,
19
+ NewOperationSchema as OperationSchema, // Use new schema for busy-python compat
20
+ ToolSchema,
21
+ NewBusyDocumentSchema as BusyDocumentSchema, // Use new schema for busy-python compat
22
+ ToolDocumentSchema,
23
+ } from '../types/schema';
24
+
25
+ describe('Schema: Metadata', () => {
26
+ it('should require name, type, and description', () => {
27
+ const validMetadata = {
28
+ name: 'TestDocument',
29
+ type: '[Document]',
30
+ description: 'A test document',
31
+ };
32
+
33
+ const result = MetadataSchema.safeParse(validMetadata);
34
+ expect(result.success).toBe(true);
35
+ if (result.success) {
36
+ expect(result.data.name).toBe('TestDocument');
37
+ expect(result.data.type).toBe('[Document]');
38
+ expect(result.data.description).toBe('A test document');
39
+ }
40
+ });
41
+
42
+ it('should accept optional provider field', () => {
43
+ const metadataWithProvider = {
44
+ name: 'GmailTool',
45
+ type: '[Tool]',
46
+ description: 'Gmail integration tool',
47
+ provider: 'composio',
48
+ };
49
+
50
+ const result = MetadataSchema.safeParse(metadataWithProvider);
51
+ expect(result.success).toBe(true);
52
+ if (result.success) {
53
+ expect(result.data.provider).toBe('composio');
54
+ }
55
+ });
56
+
57
+ it('should reject metadata without required fields', () => {
58
+ const missingName = {
59
+ type: '[Document]',
60
+ description: 'Missing name',
61
+ };
62
+
63
+ const result = MetadataSchema.safeParse(missingName);
64
+ expect(result.success).toBe(false);
65
+ });
66
+
67
+ it('should reject empty name or description', () => {
68
+ const emptyName = {
69
+ name: '',
70
+ type: '[Document]',
71
+ description: 'Valid description',
72
+ };
73
+
74
+ const result = MetadataSchema.safeParse(emptyName);
75
+ expect(result.success).toBe(false);
76
+ });
77
+
78
+ it('should NOT have Extends or Tags fields (removed from busy-python)', () => {
79
+ const metadataWithExtends = {
80
+ name: 'Test',
81
+ type: '[Document]',
82
+ description: 'Test',
83
+ extends: ['Parent'], // This should be ignored or rejected
84
+ tags: ['tag1'], // This should be ignored or rejected
85
+ };
86
+
87
+ const result = MetadataSchema.safeParse(metadataWithExtends);
88
+ // Should parse but not include extends/tags in the result
89
+ if (result.success) {
90
+ expect(result.data).not.toHaveProperty('extends');
91
+ expect(result.data).not.toHaveProperty('tags');
92
+ }
93
+ });
94
+ });
95
+
96
+ describe('Schema: Import', () => {
97
+ it('should parse import with concept name and path', () => {
98
+ const validImport = {
99
+ conceptName: 'Operation',
100
+ path: './operation.busy.md',
101
+ };
102
+
103
+ const result = ImportSchema.safeParse(validImport);
104
+ expect(result.success).toBe(true);
105
+ if (result.success) {
106
+ expect(result.data.conceptName).toBe('Operation');
107
+ expect(result.data.path).toBe('./operation.busy.md');
108
+ expect(result.data.anchor).toBeUndefined();
109
+ }
110
+ });
111
+
112
+ it('should parse import with anchor', () => {
113
+ const importWithAnchor = {
114
+ conceptName: 'RunChecklist',
115
+ path: './checklist.busy.md',
116
+ anchor: 'runchecklist',
117
+ };
118
+
119
+ const result = ImportSchema.safeParse(importWithAnchor);
120
+ expect(result.success).toBe(true);
121
+ if (result.success) {
122
+ expect(result.data.anchor).toBe('runchecklist');
123
+ }
124
+ });
125
+
126
+ it('should require concept name and path', () => {
127
+ const missingPath = {
128
+ conceptName: 'Test',
129
+ };
130
+
131
+ const result = ImportSchema.safeParse(missingPath);
132
+ expect(result.success).toBe(false);
133
+ });
134
+ });
135
+
136
+ describe('Schema: LocalDefinition', () => {
137
+ it('should parse local definition with name and content', () => {
138
+ const validDef = {
139
+ name: 'Capability',
140
+ content: 'A system feature or function that can be invoked.',
141
+ };
142
+
143
+ const result = LocalDefinitionSchema.safeParse(validDef);
144
+ expect(result.success).toBe(true);
145
+ if (result.success) {
146
+ expect(result.data.name).toBe('Capability');
147
+ expect(result.data.content).toBe('A system feature or function that can be invoked.');
148
+ }
149
+ });
150
+
151
+ it('should allow empty content', () => {
152
+ const emptyContent = {
153
+ name: 'Placeholder',
154
+ content: '',
155
+ };
156
+
157
+ const result = LocalDefinitionSchema.safeParse(emptyContent);
158
+ expect(result.success).toBe(true);
159
+ });
160
+ });
161
+
162
+ describe('Schema: Step', () => {
163
+ it('should parse step with number and instruction', () => {
164
+ const validStep = {
165
+ stepNumber: 1,
166
+ instruction: 'Parse the document frontmatter',
167
+ };
168
+
169
+ const result = StepSchema.safeParse(validStep);
170
+ expect(result.success).toBe(true);
171
+ if (result.success) {
172
+ expect(result.data.stepNumber).toBe(1);
173
+ expect(result.data.instruction).toBe('Parse the document frontmatter');
174
+ }
175
+ });
176
+
177
+ it('should parse step with operation references', () => {
178
+ const stepWithRefs = {
179
+ stepNumber: 2,
180
+ instruction: 'Execute [ValidateInput] then run [ProcessData]',
181
+ operationReferences: ['ValidateInput', 'ProcessData'],
182
+ };
183
+
184
+ const result = StepSchema.safeParse(stepWithRefs);
185
+ expect(result.success).toBe(true);
186
+ if (result.success) {
187
+ expect(result.data.operationReferences).toEqual(['ValidateInput', 'ProcessData']);
188
+ }
189
+ });
190
+
191
+ it('should require step number >= 1', () => {
192
+ const invalidStep = {
193
+ stepNumber: 0,
194
+ instruction: 'Invalid step',
195
+ };
196
+
197
+ const result = StepSchema.safeParse(invalidStep);
198
+ expect(result.success).toBe(false);
199
+ });
200
+
201
+ it('should require non-empty instruction', () => {
202
+ const emptyInstruction = {
203
+ stepNumber: 1,
204
+ instruction: '',
205
+ };
206
+
207
+ const result = StepSchema.safeParse(emptyInstruction);
208
+ expect(result.success).toBe(false);
209
+ });
210
+ });
211
+
212
+ describe('Schema: Checklist', () => {
213
+ it('should parse checklist with items', () => {
214
+ const validChecklist = {
215
+ items: ['Input validated', 'Output generated', 'Logs written'],
216
+ };
217
+
218
+ const result = ChecklistSchema.safeParse(validChecklist);
219
+ expect(result.success).toBe(true);
220
+ if (result.success) {
221
+ expect(result.data.items).toHaveLength(3);
222
+ }
223
+ });
224
+
225
+ it('should allow empty checklist', () => {
226
+ const emptyChecklist = {
227
+ items: [],
228
+ };
229
+
230
+ const result = ChecklistSchema.safeParse(emptyChecklist);
231
+ expect(result.success).toBe(true);
232
+ });
233
+ });
234
+
235
+ describe('Schema: Trigger', () => {
236
+ it('should parse time-based alarm trigger', () => {
237
+ const alarmTrigger = {
238
+ rawText: 'Set alarm for 6am each morning to run DailyReview',
239
+ triggerType: 'alarm',
240
+ schedule: '0 6 * * *',
241
+ operation: 'DailyReview',
242
+ };
243
+
244
+ const result = TriggerSchema.safeParse(alarmTrigger);
245
+ expect(result.success).toBe(true);
246
+ if (result.success) {
247
+ expect(result.data.triggerType).toBe('alarm');
248
+ expect(result.data.schedule).toBe('0 6 * * *');
249
+ expect(result.data.operation).toBe('DailyReview');
250
+ }
251
+ });
252
+
253
+ it('should parse event-based trigger', () => {
254
+ const eventTrigger = {
255
+ rawText: 'When gmail.message.received from *@lead.com, run ProcessLead',
256
+ triggerType: 'event',
257
+ eventType: 'gmail.message.received',
258
+ filter: { from: '*@lead.com' },
259
+ operation: 'ProcessLead',
260
+ };
261
+
262
+ const result = TriggerSchema.safeParse(eventTrigger);
263
+ expect(result.success).toBe(true);
264
+ if (result.success) {
265
+ expect(result.data.triggerType).toBe('event');
266
+ expect(result.data.eventType).toBe('gmail.message.received');
267
+ expect(result.data.filter).toEqual({ from: '*@lead.com' });
268
+ }
269
+ });
270
+
271
+ it('should have queueWhenPaused default to true', () => {
272
+ const trigger = {
273
+ rawText: 'When event, run Op',
274
+ triggerType: 'event',
275
+ eventType: 'test.event',
276
+ operation: 'Op',
277
+ };
278
+
279
+ const result = TriggerSchema.safeParse(trigger);
280
+ expect(result.success).toBe(true);
281
+ if (result.success) {
282
+ expect(result.data.queueWhenPaused).toBe(true);
283
+ }
284
+ });
285
+ });
286
+
287
+ describe('Schema: Operation', () => {
288
+ it('should parse operation with all fields', () => {
289
+ const validOperation = {
290
+ name: 'ExecuteTask',
291
+ inputs: ['task_name: Name of the task', 'context: Execution context'],
292
+ outputs: ['result: The computed result'],
293
+ steps: [
294
+ { stepNumber: 1, instruction: 'Validate inputs' },
295
+ { stepNumber: 2, instruction: 'Execute task' },
296
+ ],
297
+ checklist: { items: ['Input validated', 'Task executed'] },
298
+ };
299
+
300
+ const result = OperationSchema.safeParse(validOperation);
301
+ expect(result.success).toBe(true);
302
+ if (result.success) {
303
+ expect(result.data.name).toBe('ExecuteTask');
304
+ expect(result.data.inputs).toHaveLength(2);
305
+ expect(result.data.outputs).toHaveLength(1);
306
+ expect(result.data.steps).toHaveLength(2);
307
+ expect(result.data.checklist?.items).toHaveLength(2);
308
+ }
309
+ });
310
+
311
+ it('should allow operation with empty inputs/outputs/steps', () => {
312
+ const minimalOperation = {
313
+ name: 'SimpleOp',
314
+ inputs: [],
315
+ outputs: [],
316
+ steps: [],
317
+ };
318
+
319
+ const result = OperationSchema.safeParse(minimalOperation);
320
+ expect(result.success).toBe(true);
321
+ });
322
+
323
+ it('should allow optional checklist', () => {
324
+ const noChecklist = {
325
+ name: 'NoChecklist',
326
+ inputs: [],
327
+ outputs: [],
328
+ steps: [{ stepNumber: 1, instruction: 'Do something' }],
329
+ };
330
+
331
+ const result = OperationSchema.safeParse(noChecklist);
332
+ expect(result.success).toBe(true);
333
+ if (result.success) {
334
+ expect(result.data.checklist).toBeUndefined();
335
+ }
336
+ });
337
+ });
338
+
339
+ describe('Schema: Tool', () => {
340
+ it('should parse tool with all fields', () => {
341
+ const validTool = {
342
+ name: 'send_email',
343
+ description: 'Send an email to recipients',
344
+ inputs: ['to: Recipient email', 'subject: Email subject', 'body: Email content'],
345
+ outputs: ['message_id: Sent message ID'],
346
+ examples: ['send_email(to="user@example.com", subject="Hi", body="Hello")'],
347
+ providers: {
348
+ composio: {
349
+ action: 'GMAIL_SEND_EMAIL',
350
+ parameters: { to: 'to', subject: 'subject', body: 'body' },
351
+ },
352
+ },
353
+ };
354
+
355
+ const result = ToolSchema.safeParse(validTool);
356
+ expect(result.success).toBe(true);
357
+ if (result.success) {
358
+ expect(result.data.name).toBe('send_email');
359
+ expect(result.data.providers?.composio?.action).toBe('GMAIL_SEND_EMAIL');
360
+ }
361
+ });
362
+
363
+ it('should allow tool without providers', () => {
364
+ const toolNoProviders = {
365
+ name: 'local_tool',
366
+ description: 'A local tool without external providers',
367
+ inputs: ['data: Input data'],
368
+ outputs: ['result: Output result'],
369
+ };
370
+
371
+ const result = ToolSchema.safeParse(toolNoProviders);
372
+ expect(result.success).toBe(true);
373
+ });
374
+ });
375
+
376
+ describe('Schema: BusyDocument', () => {
377
+ it('should parse complete document structure', () => {
378
+ const validDocument = {
379
+ metadata: {
380
+ name: 'TestDocument',
381
+ type: '[Document]',
382
+ description: 'A test document',
383
+ },
384
+ imports: [
385
+ { conceptName: 'Concept', path: './concept.busy.md' },
386
+ ],
387
+ definitions: [
388
+ { name: 'LocalDef', content: 'A local definition' },
389
+ ],
390
+ setup: 'Initialize the document context.',
391
+ operations: [
392
+ {
393
+ name: 'TestOp',
394
+ inputs: [],
395
+ outputs: [],
396
+ steps: [{ stepNumber: 1, instruction: 'Do something' }],
397
+ },
398
+ ],
399
+ triggers: [],
400
+ };
401
+
402
+ const result = BusyDocumentSchema.safeParse(validDocument);
403
+ expect(result.success).toBe(true);
404
+ if (result.success) {
405
+ expect(result.data.metadata.name).toBe('TestDocument');
406
+ expect(result.data.imports).toHaveLength(1);
407
+ expect(result.data.definitions).toHaveLength(1);
408
+ expect(result.data.setup).toBe('Initialize the document context.');
409
+ expect(result.data.operations).toHaveLength(1);
410
+ }
411
+ });
412
+
413
+ it('should allow optional setup', () => {
414
+ const noSetup = {
415
+ metadata: {
416
+ name: 'NoSetup',
417
+ type: '[Document]',
418
+ description: 'Document without setup',
419
+ },
420
+ imports: [],
421
+ definitions: [],
422
+ operations: [],
423
+ triggers: [],
424
+ };
425
+
426
+ const result = BusyDocumentSchema.safeParse(noSetup);
427
+ expect(result.success).toBe(true);
428
+ if (result.success) {
429
+ expect(result.data.setup).toBeUndefined();
430
+ }
431
+ });
432
+ });
433
+
434
+ describe('Schema: ToolDocument', () => {
435
+ it('should parse tool document with tools array', () => {
436
+ const validToolDoc = {
437
+ metadata: {
438
+ name: 'GmailTools',
439
+ type: '[Tool]',
440
+ description: 'Gmail integration tools',
441
+ provider: 'composio',
442
+ },
443
+ imports: [],
444
+ definitions: [],
445
+ operations: [],
446
+ triggers: [],
447
+ tools: [
448
+ {
449
+ name: 'send_email',
450
+ description: 'Send an email',
451
+ inputs: ['to: Recipient'],
452
+ outputs: ['message_id: Message ID'],
453
+ providers: {
454
+ composio: { action: 'GMAIL_SEND_EMAIL', parameters: { to: 'to' } },
455
+ },
456
+ },
457
+ ],
458
+ };
459
+
460
+ const result = ToolDocumentSchema.safeParse(validToolDoc);
461
+ expect(result.success).toBe(true);
462
+ if (result.success) {
463
+ expect(result.data.tools).toHaveLength(1);
464
+ expect(result.data.metadata.provider).toBe('composio');
465
+ }
466
+ });
467
+ });