@output.ai/cli 0.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 (37) hide show
  1. package/README.md +55 -0
  2. package/bin/dev.cmd +3 -0
  3. package/bin/dev.js +5 -0
  4. package/bin/run.cmd +3 -0
  5. package/bin/run.js +5 -0
  6. package/dist/commands/workflow/generate.d.ts +16 -0
  7. package/dist/commands/workflow/generate.js +74 -0
  8. package/dist/commands/workflow/generate.spec.d.ts +1 -0
  9. package/dist/commands/workflow/generate.spec.js +119 -0
  10. package/dist/index.d.ts +1 -0
  11. package/dist/index.js +1 -0
  12. package/dist/index.spec.d.ts +1 -0
  13. package/dist/index.spec.js +6 -0
  14. package/dist/services/template_processor.d.ts +14 -0
  15. package/dist/services/template_processor.js +42 -0
  16. package/dist/services/workflow_generator.d.ts +5 -0
  17. package/dist/services/workflow_generator.js +40 -0
  18. package/dist/templates/workflow/.env.template +7 -0
  19. package/dist/templates/workflow/README.md.template +215 -0
  20. package/dist/templates/workflow/prompt@v1.prompt.template +13 -0
  21. package/dist/templates/workflow/steps.ts.template +77 -0
  22. package/dist/templates/workflow/workflow.ts.template +73 -0
  23. package/dist/types/errors.d.ts +24 -0
  24. package/dist/types/errors.js +35 -0
  25. package/dist/types/generator.d.ts +26 -0
  26. package/dist/types/generator.js +1 -0
  27. package/dist/utils/paths.d.ts +24 -0
  28. package/dist/utils/paths.js +35 -0
  29. package/dist/utils/template.d.ts +9 -0
  30. package/dist/utils/template.js +30 -0
  31. package/dist/utils/template.spec.d.ts +1 -0
  32. package/dist/utils/template.spec.js +71 -0
  33. package/dist/utils/validation.d.ts +13 -0
  34. package/dist/utils/validation.js +25 -0
  35. package/dist/utils/validation.spec.d.ts +1 -0
  36. package/dist/utils/validation.spec.js +137 -0
  37. package/package.json +60 -0
@@ -0,0 +1,77 @@
1
+ import { step } from '@output.ai/core';
2
+ import { loadPrompt } from '@output.ai/prompt';
3
+ import { generateText } from '@output.ai/llm';
4
+ import type { Prompt } from '@output.ai/llm';
5
+
6
+ // Define input/output schemas for the LLM step
7
+ const llmInputSchema = {
8
+ type: 'object',
9
+ properties: {
10
+ userInput: { type: 'string' }
11
+ },
12
+ required: ['userInput']
13
+ };
14
+
15
+ const llmOutputSchema = {
16
+ type: 'string'
17
+ };
18
+
19
+ // Example step using LLM
20
+ export const exampleLLMStep = step( {
21
+ name: 'exampleLLMStep',
22
+ description: 'Example step that uses LLM',
23
+ inputSchema: llmInputSchema,
24
+ outputSchema: llmOutputSchema,
25
+ fn: async ( input: { userInput: string } ): Promise<string> => {
26
+ // Load the prompt template
27
+ const prompt = loadPrompt( 'prompt@v1', {
28
+ userInput: input.userInput
29
+ } );
30
+
31
+ // Generate text using LLM
32
+ const response = await generateText( prompt as Prompt );
33
+
34
+ return response;
35
+ }
36
+ } );
37
+
38
+ // Define schemas for data processing step
39
+ const processDataInputSchema = {
40
+ type: 'object',
41
+ properties: {
42
+ value: { type: 'number' },
43
+ type: { type: 'string' }
44
+ },
45
+ required: ['value', 'type']
46
+ };
47
+
48
+ const processDataOutputSchema = {
49
+ type: 'object',
50
+ properties: {
51
+ processed: { type: 'boolean' },
52
+ timestamp: { type: 'string' },
53
+ data: { type: 'object' }
54
+ },
55
+ required: ['processed', 'timestamp', 'data']
56
+ };
57
+
58
+ // Example data processing step
59
+ export const processDataStep = step( {
60
+ name: 'processDataStep',
61
+ description: 'Process input data',
62
+ inputSchema: processDataInputSchema,
63
+ outputSchema: processDataOutputSchema,
64
+ fn: async ( input: { value: number; type: string } ) => {
65
+ // TODO: Implement your step logic here
66
+ console.log( 'Processing data:', input );
67
+
68
+ // Example processing
69
+ return {
70
+ processed: true,
71
+ timestamp: new Date().toISOString(),
72
+ data: input
73
+ };
74
+ }
75
+ } );
76
+
77
+ // Add more steps as needed
@@ -0,0 +1,73 @@
1
+ import { workflow } from '@output.ai/core';
2
+ import { exampleLLMStep, processDataStep } from './steps.js';
3
+
4
+ // Define the input schema for the workflow
5
+ const inputSchema = {
6
+ type: 'object',
7
+ properties: {
8
+ prompt: {
9
+ type: 'string',
10
+ description: 'The prompt to send to the LLM'
11
+ },
12
+ data: {
13
+ type: 'object',
14
+ properties: {
15
+ value: { type: 'number' },
16
+ type: { type: 'string' }
17
+ },
18
+ required: ['value', 'type']
19
+ }
20
+ },
21
+ required: ['prompt']
22
+ };
23
+
24
+ // Define the output schema for the workflow
25
+ const outputSchema = {
26
+ type: 'object',
27
+ properties: {
28
+ llmResponse: { type: 'string' },
29
+ processedData: {
30
+ type: 'object',
31
+ properties: {
32
+ processed: { type: 'boolean' },
33
+ timestamp: { type: 'string' },
34
+ data: { type: 'object' }
35
+ }
36
+ },
37
+ message: { type: 'string' }
38
+ },
39
+ required: ['llmResponse', 'processedData', 'message']
40
+ };
41
+
42
+ // Define the input type based on the schema
43
+ interface WorkflowInput {
44
+ prompt: string;
45
+ data?: {
46
+ value: number;
47
+ type: string;
48
+ };
49
+ }
50
+
51
+ export default workflow( {
52
+ name: '{{workflowName}}',
53
+ description: '{{description}}',
54
+ inputSchema,
55
+ outputSchema,
56
+ fn: async ( input: WorkflowInput ) => {
57
+ // Call the LLM step with input from workflow
58
+ const llmResponse = await exampleLLMStep( {
59
+ userInput: input.prompt
60
+ } );
61
+
62
+ // Process data if provided, otherwise use defaults
63
+ const processedData = await processDataStep(
64
+ input.data || { value: 42, type: 'default' }
65
+ );
66
+
67
+ return {
68
+ llmResponse,
69
+ processedData,
70
+ message: 'Workflow {{workflowName}} executed successfully'
71
+ };
72
+ }
73
+ } );
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Error thrown when a workflow already exists
3
+ */
4
+ export declare class WorkflowExistsError extends Error {
5
+ constructor(workflowName: string, targetPath: string);
6
+ }
7
+ /**
8
+ * Error thrown when workflow name is invalid
9
+ */
10
+ export declare class InvalidNameError extends Error {
11
+ constructor(name: string);
12
+ }
13
+ /**
14
+ * Error thrown when template file is not found
15
+ */
16
+ export declare class TemplateNotFoundError extends Error {
17
+ constructor(templateFile: string);
18
+ }
19
+ /**
20
+ * Error thrown when output directory is invalid or inaccessible
21
+ */
22
+ export declare class InvalidOutputDirectoryError extends Error {
23
+ constructor(outputDir: string, reason?: string);
24
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Error thrown when a workflow already exists
3
+ */
4
+ export class WorkflowExistsError extends Error {
5
+ constructor(workflowName, targetPath) {
6
+ super(`Workflow "${workflowName}" already exists at ${targetPath}. Use --force to overwrite.`);
7
+ }
8
+ }
9
+ /**
10
+ * Error thrown when workflow name is invalid
11
+ */
12
+ export class InvalidNameError extends Error {
13
+ constructor(name) {
14
+ super(`Invalid workflow name "${name}". Name must contain only letters, numbers, hyphens, and underscores.`);
15
+ }
16
+ }
17
+ /**
18
+ * Error thrown when template file is not found
19
+ */
20
+ export class TemplateNotFoundError extends Error {
21
+ constructor(templateFile) {
22
+ super(`Template file "${templateFile}" not found. Please ensure CLI is properly installed.`);
23
+ }
24
+ }
25
+ /**
26
+ * Error thrown when output directory is invalid or inaccessible
27
+ */
28
+ export class InvalidOutputDirectoryError extends Error {
29
+ constructor(outputDir, reason) {
30
+ const message = reason ?
31
+ `Invalid output directory "${outputDir}": ${reason}` :
32
+ `Invalid output directory "${outputDir}"`;
33
+ super(message);
34
+ }
35
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Configuration for workflow generation
3
+ */
4
+ export interface WorkflowGenerationConfig {
5
+ name: string;
6
+ description?: string;
7
+ outputDir: string;
8
+ skeleton: boolean;
9
+ force: boolean;
10
+ }
11
+ /**
12
+ * Result of workflow generation
13
+ */
14
+ export interface WorkflowGenerationResult {
15
+ workflowName: string;
16
+ targetDir: string;
17
+ filesCreated: string[];
18
+ }
19
+ /**
20
+ * Template file information
21
+ */
22
+ export interface TemplateFile {
23
+ name: string;
24
+ path: string;
25
+ outputName: string;
26
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Template directory paths
3
+ */
4
+ export declare const TEMPLATE_DIRS: {
5
+ readonly workflow: string;
6
+ };
7
+ /**
8
+ * Default output directories
9
+ */
10
+ export declare const DEFAULT_OUTPUT_DIRS: {
11
+ readonly workflows: "output-workflows/src";
12
+ };
13
+ /**
14
+ * Resolve the output directory path
15
+ */
16
+ export declare function resolveOutputDir(outputDir: string): string;
17
+ /**
18
+ * Create target directory path for a workflow
19
+ */
20
+ export declare function createTargetDir(outputDir: string, workflowName: string): string;
21
+ /**
22
+ * Get the template directory for a specific template type
23
+ */
24
+ export declare function getTemplateDir(templateType: keyof typeof TEMPLATE_DIRS): string;
@@ -0,0 +1,35 @@
1
+ import * as path from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ const __filename = fileURLToPath(import.meta.url);
4
+ const __dirname = path.dirname(__filename);
5
+ /**
6
+ * Template directory paths
7
+ */
8
+ export const TEMPLATE_DIRS = {
9
+ workflow: path.join(__dirname, '..', 'templates', 'workflow')
10
+ };
11
+ /**
12
+ * Default output directories
13
+ */
14
+ export const DEFAULT_OUTPUT_DIRS = {
15
+ workflows: 'output-workflows/src'
16
+ };
17
+ /**
18
+ * Resolve the output directory path
19
+ */
20
+ export function resolveOutputDir(outputDir) {
21
+ return path.resolve(process.cwd(), outputDir);
22
+ }
23
+ /**
24
+ * Create target directory path for a workflow
25
+ */
26
+ export function createTargetDir(outputDir, workflowName) {
27
+ const resolvedOutputDir = resolveOutputDir(outputDir);
28
+ return path.join(resolvedOutputDir, workflowName);
29
+ }
30
+ /**
31
+ * Get the template directory for a specific template type
32
+ */
33
+ export function getTemplateDir(templateType) {
34
+ return TEMPLATE_DIRS[templateType];
35
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Process a template string with variables
3
+ */
4
+ export declare function processTemplate(templateContent: string, variables: Record<string, string>): string;
5
+ /**
6
+ * Prepare template variables from workflow name and description
7
+ */
8
+ export declare function prepareTemplateVariables(workflowName: string, description: string): Record<string, string>;
9
+ export { camelCase, pascalCase } from 'change-case';
@@ -0,0 +1,30 @@
1
+ import Handlebars from 'handlebars';
2
+ import { camelCase, pascalCase } from 'change-case';
3
+ /**
4
+ * Create a Handlebars compiler with custom helpers
5
+ */
6
+ function createCompiler() {
7
+ const compiler = Handlebars.create();
8
+ compiler.registerHelper('camelCase', (str) => camelCase(str));
9
+ compiler.registerHelper('pascalCase', (str) => pascalCase(str));
10
+ return compiler;
11
+ }
12
+ /**
13
+ * Process a template string with variables
14
+ */
15
+ export function processTemplate(templateContent, variables) {
16
+ const compiler = createCompiler();
17
+ const template = compiler.compile(templateContent);
18
+ return template(variables);
19
+ }
20
+ /**
21
+ * Prepare template variables from workflow name and description
22
+ */
23
+ export function prepareTemplateVariables(workflowName, description) {
24
+ return {
25
+ workflowName: camelCase(workflowName),
26
+ WorkflowName: pascalCase(workflowName),
27
+ description: description || `A ${workflowName} workflow`
28
+ };
29
+ }
30
+ export { camelCase, pascalCase } from 'change-case';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,71 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { camelCase, pascalCase, processTemplate, prepareTemplateVariables } from './template';
3
+ describe('Template Utilities', () => {
4
+ describe('pascalCase', () => {
5
+ it('should convert kebab-case to PascalCase', () => {
6
+ expect(pascalCase('my-workflow-name')).toBe('MyWorkflowName');
7
+ });
8
+ it('should convert snake_case to PascalCase', () => {
9
+ expect(pascalCase('my_workflow_name')).toBe('MyWorkflowName');
10
+ });
11
+ it('should handle single word', () => {
12
+ expect(pascalCase('workflow')).toBe('Workflow');
13
+ });
14
+ it('should handle mixed separators', () => {
15
+ expect(pascalCase('my-workflow_name')).toBe('MyWorkflowName');
16
+ });
17
+ });
18
+ describe('camelCase', () => {
19
+ it('should convert kebab-case to camelCase', () => {
20
+ expect(camelCase('my-workflow-name')).toBe('myWorkflowName');
21
+ });
22
+ it('should convert snake_case to camelCase', () => {
23
+ expect(camelCase('my_workflow_name')).toBe('myWorkflowName');
24
+ });
25
+ it('should handle single word', () => {
26
+ expect(camelCase('workflow')).toBe('workflow');
27
+ });
28
+ });
29
+ describe('processTemplate', () => {
30
+ it('should replace single variable', () => {
31
+ const template = 'Hello {{name}}!';
32
+ const variables = { name: 'World' };
33
+ expect(processTemplate(template, variables)).toBe('Hello World!');
34
+ });
35
+ it('should replace multiple variables', () => {
36
+ const template = 'Workflow {{workflowName}} - {{description}}';
37
+ const variables = {
38
+ workflowName: 'myWorkflow',
39
+ description: 'A test workflow'
40
+ };
41
+ expect(processTemplate(template, variables))
42
+ .toBe('Workflow myWorkflow - A test workflow');
43
+ });
44
+ it('should replace multiple occurrences of same variable', () => {
45
+ const template = '{{name}} says hello to {{name}}';
46
+ const variables = { name: 'Alice' };
47
+ expect(processTemplate(template, variables))
48
+ .toBe('Alice says hello to Alice');
49
+ });
50
+ it('should use Handlebars helpers for case transformations', () => {
51
+ const template = '{{camelCase name}} and {{pascalCase name}}';
52
+ const variables = { name: 'my-workflow-name' };
53
+ expect(processTemplate(template, variables))
54
+ .toBe('myWorkflowName and MyWorkflowName');
55
+ });
56
+ });
57
+ describe('prepareTemplateVariables', () => {
58
+ it('should prepare variables from workflow name and description', () => {
59
+ const variables = prepareTemplateVariables('my-test-workflow', 'A test workflow for testing');
60
+ expect(variables).toEqual({
61
+ workflowName: 'myTestWorkflow',
62
+ WorkflowName: 'MyTestWorkflow',
63
+ description: 'A test workflow for testing'
64
+ });
65
+ });
66
+ it('should provide default description if not provided', () => {
67
+ const variables = prepareTemplateVariables('test-workflow', '');
68
+ expect(variables.description).toBe('A test-workflow workflow');
69
+ });
70
+ });
71
+ });
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Validate workflow name format
3
+ * Must contain only letters, numbers, hyphens, and underscores
4
+ */
5
+ export declare function isValidWorkflowName(name: string): boolean;
6
+ /**
7
+ * Validate workflow name and throw descriptive error if invalid
8
+ */
9
+ export declare function validateWorkflowName(name: string): void;
10
+ /**
11
+ * Validate that a directory path is safe to create
12
+ */
13
+ export declare function validateOutputDirectory(outputDir: string): void;
@@ -0,0 +1,25 @@
1
+ import { InvalidNameError, InvalidOutputDirectoryError } from '../types/errors.js';
2
+ /**
3
+ * Validate workflow name format
4
+ * Must contain only letters, numbers, hyphens, and underscores
5
+ */
6
+ export function isValidWorkflowName(name) {
7
+ // Must start with a letter or underscore, followed by any number of letters, numbers, or underscores
8
+ return /^[a-z_][a-z0-9_]*$/i.test(name);
9
+ }
10
+ /**
11
+ * Validate workflow name and throw descriptive error if invalid
12
+ */
13
+ export function validateWorkflowName(name) {
14
+ if (!isValidWorkflowName(name)) {
15
+ throw new InvalidNameError(name);
16
+ }
17
+ }
18
+ /**
19
+ * Validate that a directory path is safe to create
20
+ */
21
+ export function validateOutputDirectory(outputDir) {
22
+ if (!outputDir || outputDir.trim() === '') {
23
+ throw new InvalidOutputDirectoryError(outputDir, 'Output directory cannot be empty');
24
+ }
25
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,137 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { isValidWorkflowName } from './validation.js';
3
+ describe('isValidWorkflowName', () => {
4
+ describe('valid workflow names', () => {
5
+ it('should accept single letter', () => {
6
+ expect(isValidWorkflowName('a')).toBe(true);
7
+ expect(isValidWorkflowName('Z')).toBe(true);
8
+ });
9
+ it('should accept single underscore', () => {
10
+ expect(isValidWorkflowName('_')).toBe(true);
11
+ });
12
+ it('should accept names starting with letter', () => {
13
+ expect(isValidWorkflowName('workflow')).toBe(true);
14
+ expect(isValidWorkflowName('MyWorkflow')).toBe(true);
15
+ expect(isValidWorkflowName('simpleWorkflow123')).toBe(true);
16
+ });
17
+ it('should accept names starting with underscore', () => {
18
+ expect(isValidWorkflowName('_workflow')).toBe(true);
19
+ expect(isValidWorkflowName('_private_workflow')).toBe(true);
20
+ expect(isValidWorkflowName('__double_underscore')).toBe(true);
21
+ });
22
+ it('should accept names with numbers after first character', () => {
23
+ expect(isValidWorkflowName('workflow1')).toBe(true);
24
+ expect(isValidWorkflowName('workflow_123')).toBe(true);
25
+ expect(isValidWorkflowName('a123456789')).toBe(true);
26
+ expect(isValidWorkflowName('_123')).toBe(true);
27
+ });
28
+ it('should accept names with underscores in any position', () => {
29
+ expect(isValidWorkflowName('my_workflow')).toBe(true);
30
+ expect(isValidWorkflowName('workflow_name_here')).toBe(true);
31
+ expect(isValidWorkflowName('a_b_c_d')).toBe(true);
32
+ expect(isValidWorkflowName('workflow__double')).toBe(true);
33
+ });
34
+ it('should accept mixed case names', () => {
35
+ expect(isValidWorkflowName('WorkFlow')).toBe(true);
36
+ expect(isValidWorkflowName('myWorkFlow')).toBe(true);
37
+ expect(isValidWorkflowName('MY_WORKFLOW')).toBe(true);
38
+ expect(isValidWorkflowName('CamelCaseWorkflow')).toBe(true);
39
+ });
40
+ it('should accept long valid names', () => {
41
+ const longName = 'a'.repeat(100) + '_' + '1'.repeat(100);
42
+ expect(isValidWorkflowName(longName)).toBe(true);
43
+ });
44
+ });
45
+ describe('invalid workflow names', () => {
46
+ it('should reject empty string', () => {
47
+ expect(isValidWorkflowName('')).toBe(false);
48
+ });
49
+ it('should reject names starting with numbers', () => {
50
+ expect(isValidWorkflowName('1workflow')).toBe(false);
51
+ expect(isValidWorkflowName('123')).toBe(false);
52
+ expect(isValidWorkflowName('0_workflow')).toBe(false);
53
+ expect(isValidWorkflowName('9abc')).toBe(false);
54
+ });
55
+ it('should reject names with hyphens', () => {
56
+ expect(isValidWorkflowName('my-workflow')).toBe(false);
57
+ expect(isValidWorkflowName('workflow-name')).toBe(false);
58
+ expect(isValidWorkflowName('-workflow')).toBe(false);
59
+ expect(isValidWorkflowName('workflow-')).toBe(false);
60
+ });
61
+ it('should reject names with spaces', () => {
62
+ expect(isValidWorkflowName('my workflow')).toBe(false);
63
+ expect(isValidWorkflowName(' workflow')).toBe(false);
64
+ expect(isValidWorkflowName('workflow ')).toBe(false);
65
+ expect(isValidWorkflowName('work flow')).toBe(false);
66
+ });
67
+ it('should reject names with special characters', () => {
68
+ expect(isValidWorkflowName('workflow!')).toBe(false);
69
+ expect(isValidWorkflowName('work@flow')).toBe(false);
70
+ expect(isValidWorkflowName('workflow#123')).toBe(false);
71
+ expect(isValidWorkflowName('work$flow')).toBe(false);
72
+ expect(isValidWorkflowName('workflow%')).toBe(false);
73
+ expect(isValidWorkflowName('work^flow')).toBe(false);
74
+ expect(isValidWorkflowName('workflow&')).toBe(false);
75
+ expect(isValidWorkflowName('work*flow')).toBe(false);
76
+ expect(isValidWorkflowName('workflow(')).toBe(false);
77
+ expect(isValidWorkflowName('work)flow')).toBe(false);
78
+ expect(isValidWorkflowName('work+flow')).toBe(false);
79
+ expect(isValidWorkflowName('work=flow')).toBe(false);
80
+ expect(isValidWorkflowName('work[flow')).toBe(false);
81
+ expect(isValidWorkflowName('work]flow')).toBe(false);
82
+ expect(isValidWorkflowName('work{flow')).toBe(false);
83
+ expect(isValidWorkflowName('work}flow')).toBe(false);
84
+ expect(isValidWorkflowName('work|flow')).toBe(false);
85
+ expect(isValidWorkflowName('work\\flow')).toBe(false);
86
+ expect(isValidWorkflowName('work/flow')).toBe(false);
87
+ expect(isValidWorkflowName('work:flow')).toBe(false);
88
+ expect(isValidWorkflowName('work;flow')).toBe(false);
89
+ expect(isValidWorkflowName('work"flow')).toBe(false);
90
+ expect(isValidWorkflowName('work\'flow')).toBe(false);
91
+ expect(isValidWorkflowName('work<flow')).toBe(false);
92
+ expect(isValidWorkflowName('work>flow')).toBe(false);
93
+ expect(isValidWorkflowName('work,flow')).toBe(false);
94
+ expect(isValidWorkflowName('work.flow')).toBe(false);
95
+ expect(isValidWorkflowName('work?flow')).toBe(false);
96
+ expect(isValidWorkflowName('work`flow')).toBe(false);
97
+ expect(isValidWorkflowName('work~flow')).toBe(false);
98
+ });
99
+ it('should reject names with unicode characters', () => {
100
+ expect(isValidWorkflowName('wørkflow')).toBe(false);
101
+ expect(isValidWorkflowName('работа')).toBe(false);
102
+ expect(isValidWorkflowName('工作流')).toBe(false);
103
+ expect(isValidWorkflowName('workflow™')).toBe(false);
104
+ expect(isValidWorkflowName('work✓flow')).toBe(false);
105
+ expect(isValidWorkflowName('😀workflow')).toBe(false);
106
+ });
107
+ it('should reject names with tabs and newlines', () => {
108
+ expect(isValidWorkflowName('work\tflow')).toBe(false);
109
+ expect(isValidWorkflowName('work\nflow')).toBe(false);
110
+ expect(isValidWorkflowName('work\rflow')).toBe(false);
111
+ });
112
+ it('should handle string versions of null and undefined', () => {
113
+ // String(null) returns "null" which is actually a valid workflow name
114
+ expect(isValidWorkflowName(String(null))).toBe(true);
115
+ // String(undefined) returns "undefined" which is also a valid workflow name
116
+ expect(isValidWorkflowName(String(undefined))).toBe(true);
117
+ });
118
+ });
119
+ describe('edge cases', () => {
120
+ it('should handle boundary conditions', () => {
121
+ expect(isValidWorkflowName('a0')).toBe(true);
122
+ expect(isValidWorkflowName('_0')).toBe(true);
123
+ expect(isValidWorkflowName('Z9')).toBe(true);
124
+ expect(isValidWorkflowName('_9')).toBe(true);
125
+ });
126
+ it('should be case-insensitive for letters', () => {
127
+ expect(isValidWorkflowName('abc')).toBe(true);
128
+ expect(isValidWorkflowName('ABC')).toBe(true);
129
+ expect(isValidWorkflowName('AbC')).toBe(true);
130
+ });
131
+ it('should handle only underscores and numbers after initial character', () => {
132
+ expect(isValidWorkflowName('a_________')).toBe(true);
133
+ expect(isValidWorkflowName('_000000000')).toBe(true);
134
+ expect(isValidWorkflowName('a_0_1_2_3')).toBe(true);
135
+ });
136
+ });
137
+ });
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@output.ai/cli",
3
+ "description": "CLI for Output.ai workflow generation",
4
+ "version": "0.0.0",
5
+ "author": "Ben Church",
6
+ "bin": {
7
+ "output-cli": "./bin/run.js"
8
+ },
9
+ "bugs": "https://github.com/growthxai/flow-sdk/issues",
10
+ "dependencies": {
11
+ "@oclif/core": "^4",
12
+ "@oclif/plugin-help": "^6",
13
+ "@oclif/plugin-plugins": "^5",
14
+ "change-case": "^5.4.4",
15
+ "handlebars": "^4.7.8"
16
+ },
17
+ "devDependencies": {
18
+ "@oclif/test": "^4",
19
+ "@types/handlebars": "^4.0.40",
20
+ "@types/node": "^18",
21
+ "copyfiles": "^2.4.1",
22
+ "oclif": "^4"
23
+ },
24
+ "engines": {
25
+ "node": ">=18.0.0"
26
+ },
27
+ "files": [
28
+ "./bin",
29
+ "./dist",
30
+ "./oclif.manifest.json"
31
+ ],
32
+ "homepage": "https://github.com/growthxai/flow-sdk",
33
+ "keywords": [
34
+ "oclif"
35
+ ],
36
+ "license": "MIT",
37
+ "main": "dist/index.js",
38
+ "type": "module",
39
+ "oclif": {
40
+ "bin": "output-cli",
41
+ "dirname": "output-cli",
42
+ "commands": "./dist/commands",
43
+ "plugins": [
44
+ "@oclif/plugin-help",
45
+ "@oclif/plugin-plugins"
46
+ ],
47
+ "topicSeparator": " ",
48
+ "topics": {
49
+ "workflow": {
50
+ "description": "Manage Output.ai workflows"
51
+ }
52
+ }
53
+ },
54
+ "repository": "sdk/cli",
55
+ "scripts": {
56
+ "build": "rm -rf ./dist && tsc && copyfiles -u 1 './src/templates/**/*.template' './src/templates/**/.env.template' './src/templates/**/*.prompt.template' dist",
57
+ "test": "vitest run"
58
+ },
59
+ "types": "dist/index.d.ts"
60
+ }