@forgehive/forge-cli 0.3.18 → 0.5.1

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 (92) hide show
  1. package/dist/runner.js +68 -0
  2. package/dist/tasks/auth/add.d.ts +2 -2
  3. package/dist/tasks/auth/add.js +4 -4
  4. package/dist/tasks/auth/clear.d.ts +3 -3
  5. package/dist/tasks/auth/list.d.ts +3 -3
  6. package/dist/tasks/auth/load.d.ts +1 -1
  7. package/dist/tasks/auth/loadCurrent.d.ts +3 -3
  8. package/dist/tasks/auth/remove.d.ts +2 -2
  9. package/dist/tasks/auth/remove.js +1 -1
  10. package/dist/tasks/auth/switch.d.ts +2 -2
  11. package/dist/tasks/auth/switch.js +1 -1
  12. package/dist/tasks/bundle/create.d.ts +2 -2
  13. package/dist/tasks/bundle/create.js +2 -2
  14. package/dist/tasks/bundle/fingerprint.d.ts +2 -2
  15. package/dist/tasks/bundle/fingerprint.js +2 -2
  16. package/dist/tasks/bundle/load.js +1 -1
  17. package/dist/tasks/bundle/zip.js +4 -4
  18. package/dist/tasks/conf/info.d.ts +5 -5
  19. package/dist/tasks/conf/load.d.ts +1 -1
  20. package/dist/tasks/docs/download.js +2 -2
  21. package/dist/tasks/fixture/download.d.ts +4 -4
  22. package/dist/tasks/fixture/download.js +1 -1
  23. package/dist/tasks/init.js +1 -1
  24. package/dist/tasks/project/create.d.ts +4 -4
  25. package/dist/tasks/project/create.js +2 -2
  26. package/dist/tasks/project/link.d.ts +4 -4
  27. package/dist/tasks/project/link.js +1 -1
  28. package/dist/tasks/project/sync.d.ts +5 -5
  29. package/dist/tasks/project/unlink.d.ts +3 -3
  30. package/dist/tasks/runner/bundle.d.ts +2 -2
  31. package/dist/tasks/runner/bundle.js +2 -2
  32. package/dist/tasks/runner/create.d.ts +2 -2
  33. package/dist/tasks/runner/create.js +1 -1
  34. package/dist/tasks/runner/remove.d.ts +2 -2
  35. package/dist/tasks/runner/remove.js +1 -1
  36. package/dist/tasks/task/createTask.d.ts +4 -4
  37. package/dist/tasks/task/createTask.js +3 -2
  38. package/dist/tasks/task/describe.d.ts +2 -2
  39. package/dist/tasks/task/describe.js +1 -1
  40. package/dist/tasks/task/download.d.ts +4 -4
  41. package/dist/tasks/task/download.js +2 -2
  42. package/dist/tasks/task/fingerprint.d.ts +2 -2
  43. package/dist/tasks/task/fingerprint.js +1 -1
  44. package/dist/tasks/task/invoke.d.ts +4 -4
  45. package/dist/tasks/task/invoke.js +2 -2
  46. package/dist/tasks/task/list.d.ts +3 -3
  47. package/dist/tasks/task/publish.d.ts +4 -4
  48. package/dist/tasks/task/publish.js +1 -1
  49. package/dist/tasks/task/remove.d.ts +2 -2
  50. package/dist/tasks/task/remove.js +1 -1
  51. package/dist/tasks/task/replay.d.ts +5 -5
  52. package/dist/tasks/task/replay.js +3 -3
  53. package/dist/tasks/task/run.d.ts +4 -4
  54. package/dist/tasks/task/run.js +2 -2
  55. package/dist/test/tasks/create.test.js +3 -2
  56. package/dist/utils/taskAnalysis.d.ts +6 -0
  57. package/dist/utils/taskAnalysis.js +82 -41
  58. package/package.json +11 -11
  59. package/pnpm-workspace.yaml +2 -0
  60. package/src/runner.ts +77 -0
  61. package/src/tasks/auth/add.ts +4 -4
  62. package/src/tasks/auth/remove.ts +1 -1
  63. package/src/tasks/auth/switch.ts +1 -1
  64. package/src/tasks/bundle/create.ts +2 -2
  65. package/src/tasks/bundle/fingerprint.ts +2 -2
  66. package/src/tasks/bundle/load.ts +1 -1
  67. package/src/tasks/bundle/zip.ts +4 -4
  68. package/src/tasks/docs/download.ts +2 -2
  69. package/src/tasks/fixture/download.ts +1 -1
  70. package/src/tasks/init.ts +1 -1
  71. package/src/tasks/project/create.ts +2 -2
  72. package/src/tasks/project/link.ts +1 -1
  73. package/src/tasks/runner/bundle.ts +2 -2
  74. package/src/tasks/runner/create.ts +1 -1
  75. package/src/tasks/runner/remove.ts +1 -1
  76. package/src/tasks/task/createTask.ts +3 -2
  77. package/src/tasks/task/describe.ts +1 -1
  78. package/src/tasks/task/download.ts +2 -2
  79. package/src/tasks/task/fingerprint.ts +1 -1
  80. package/src/tasks/task/invoke.ts +2 -2
  81. package/src/tasks/task/publish.ts +1 -1
  82. package/src/tasks/task/remove.ts +1 -1
  83. package/src/tasks/task/replay.ts +3 -3
  84. package/src/tasks/task/run.ts +2 -2
  85. package/src/test/tasks/create.test.ts +3 -2
  86. package/src/utils/taskAnalysis.ts +90 -41
  87. package/logs/bundle:fingerprint.log +0 -1
  88. package/logs/task:fingerprint.log +0 -10
  89. package/logs/task:list.log +0 -1
  90. package/logs/test:guidance.log +0 -1
  91. package/logs/test:uuid.log +0 -1
  92. package/logs/test:uuidCheck.log +0 -1
@@ -9,8 +9,8 @@ import { load as loadConf } from '../conf/load'
9
9
  import { type ForgeConf } from '../types'
10
10
 
11
11
  const schema = new Schema({
12
- entryPoint: Schema.string(),
13
- outputFile: Schema.string()
12
+ entryPoint: Schema.string().describe('Path to the task entry point file'),
13
+ outputFile: Schema.string().describe('Path for the bundled output file')
14
14
  })
15
15
 
16
16
  const boundaries = {
@@ -45,8 +45,8 @@ interface FingerprintResult {
45
45
  const description = 'Generate task bundle with comprehensive fingerprinting and type extraction'
46
46
 
47
47
  const schema = new Schema({
48
- descriptorName: Schema.string(),
49
- filePath: Schema.string().optional()
48
+ descriptorName: Schema.string().describe('The task descriptor name (e.g. domain:taskName)'),
49
+ filePath: Schema.string().describe('Optional path to the task source file').optional()
50
50
  })
51
51
 
52
52
  const boundaries = {
@@ -6,7 +6,7 @@ import { createTask } from '@forgehive/task'
6
6
  import { Schema } from '@forgehive/schema'
7
7
 
8
8
  const schema = new Schema({
9
- bundlePath: Schema.string()
9
+ bundlePath: Schema.string().describe('Path to the bundle file to load')
10
10
  })
11
11
 
12
12
  const boundaries = {}
@@ -11,10 +11,10 @@ import path from 'path'
11
11
  const description = 'Zip a bundle file for distribution'
12
12
 
13
13
  const schema = new Schema({
14
- dir: Schema.string(),
15
- input: Schema.string(),
16
- output: Schema.string(),
17
- forgeJsonPath: Schema.string().optional() // Optional path to forge.json - if provided, it will be included
14
+ dir: Schema.string().describe('Directory that will contain the generated zip'),
15
+ input: Schema.string().describe('Path to the file or folder to add to the zip'),
16
+ output: Schema.string().describe('Output path for the generated zip file'),
17
+ forgeJsonPath: Schema.string().describe('Optional path to forge.json to include in the bundle').optional() // Optional path to forge.json - if provided, it will be included
18
18
  })
19
19
 
20
20
  const boundaries = {
@@ -16,8 +16,8 @@ const LLM_GUIDE_URL = 'https://raw.githubusercontent.com/forge-and-hive/forge-mo
16
16
  const LLM_HIVE_LOGGING_URL = 'https://raw.githubusercontent.com/forge-and-hive/forge-mono-repo/refs/heads/main/docs/llm-hive-logging.md'
17
17
 
18
18
  const schema = new Schema({
19
- path: Schema.string().optional(),
20
- logs: Schema.boolean().optional()
19
+ path: Schema.string().describe('Optional output path for the downloaded docs').optional(),
20
+ logs: Schema.boolean().describe('Include execution logs in the downloaded docs').optional()
21
21
  })
22
22
 
23
23
  const boundaries = {
@@ -26,7 +26,7 @@ interface FixtureResponse {
26
26
  const description = 'Download a fixture by UUID to a path based on task descriptor returned from API'
27
27
 
28
28
  const schema = new Schema({
29
- uuid: Schema.string()
29
+ uuid: Schema.string().describe('The UUID of the fixture to download')
30
30
  })
31
31
 
32
32
  const boundaries = {
package/src/tasks/init.ts CHANGED
@@ -7,7 +7,7 @@ import { Schema } from '@forgehive/schema'
7
7
  import { type ForgeConf } from './types'
8
8
 
9
9
  const schema = new Schema({
10
- dryRun: Schema.boolean().optional()
10
+ dryRun: Schema.boolean().describe('Preview the changes without writing any files').optional()
11
11
  })
12
12
 
13
13
  const boundaries = {
@@ -16,8 +16,8 @@ const taskName = 'project:create'
16
16
  const taskDescription = 'Create a new project in ForgeHive'
17
17
 
18
18
  const schema = new Schema({
19
- name: Schema.string(),
20
- description: Schema.string().optional()
19
+ name: Schema.string().describe('The name of the project'),
20
+ description: Schema.string().describe('Optional description of the project').optional()
21
21
  })
22
22
 
23
23
  const boundaries = {
@@ -15,7 +15,7 @@ const name = 'project:link'
15
15
  const description = 'Link an existing remote project to the local project by UUID'
16
16
 
17
17
  const schema = new Schema({
18
- uuid: Schema.string()
18
+ uuid: Schema.string().describe('The UUID of the project to link')
19
19
  })
20
20
 
21
21
  const boundaries = {
@@ -11,8 +11,8 @@ import { load as loadConf } from '../conf/load'
11
11
  import { type ForgeConf } from '../types'
12
12
 
13
13
  const schema = new Schema({
14
- runnerName: Schema.string(),
15
- targetPath: Schema.string()
14
+ runnerName: Schema.string().describe('The name of the runner to bundle'),
15
+ targetPath: Schema.string().describe('Target path for the bundled runner')
16
16
  })
17
17
 
18
18
  const boundaries = {
@@ -30,7 +30,7 @@ export { {{ runnerName }}Runner }
30
30
  `
31
31
 
32
32
  const schema = new Schema({
33
- runnerName: Schema.string()
33
+ runnerName: Schema.string().describe('The name of the runner to create')
34
34
  })
35
35
 
36
36
  const boundaries = {
@@ -11,7 +11,7 @@ import { load } from '../conf/load'
11
11
  import { type ForgeConf } from '../types'
12
12
 
13
13
  const schema = new Schema({
14
- runnerName: Schema.string()
14
+ runnerName: Schema.string().describe('The name of the runner to remove')
15
15
  })
16
16
 
17
17
  const boundaries = {
@@ -24,8 +24,9 @@ const name = '{{ taskDescriptor }}'
24
24
  const description = 'Add task description here'
25
25
 
26
26
  const schema = new Schema({
27
- // Add your schema definitions here
28
- // example: myParam: Schema.string()
27
+ // Add your schema definitions here.
28
+ // Use .describe() so the field shows up in \`forge {{ taskDescriptor }} --help\`.
29
+ // example: myParam: Schema.string().describe('What this parameter is for')
29
30
  })
30
31
 
31
32
  const boundaries = {
@@ -17,7 +17,7 @@ import { type ForgeConf } from '../types'
17
17
  const description = 'Describe a task with detailed information about its schema, boundaries and configuration'
18
18
 
19
19
  const schema = new Schema({
20
- descriptorName: Schema.string()
20
+ descriptorName: Schema.string().describe('The task descriptor name to describe (e.g. domain:taskName)')
21
21
  })
22
22
 
23
23
  const boundaries = {
@@ -13,8 +13,8 @@ import { loadCurrent as loadCurrentProfile } from '../auth/loadCurrent'
13
13
  import { Profile, type ForgeConf } from '../types'
14
14
 
15
15
  const schema = new Schema({
16
- descriptorName: Schema.string(),
17
- uuid: Schema.string()
16
+ descriptorName: Schema.string().describe('The task descriptor name (e.g. domain:taskName)'),
17
+ uuid: Schema.string().describe('The UUID of the task version to download')
18
18
  })
19
19
 
20
20
  const boundaries = {
@@ -12,7 +12,7 @@ import { fingerprint as bundleFingerprint } from '../bundle/fingerprint'
12
12
  const description = 'Analyze a specific task and generate detailed fingerprint without bundling'
13
13
 
14
14
  const schema = new Schema({
15
- descriptorName: Schema.string()
15
+ descriptorName: Schema.string().describe('The task descriptor name to fingerprint (e.g. domain:taskName)')
16
16
  })
17
17
 
18
18
  const boundaries = {
@@ -14,8 +14,8 @@ const name = 'task:invoke'
14
14
  const description = 'Invoke a deployed task remotely using the Hive API'
15
15
 
16
16
  const schema = new Schema({
17
- descriptorName: Schema.string(),
18
- json: Schema.string()
17
+ descriptorName: Schema.string().describe('The task descriptor name (e.g. domain:taskName)'),
18
+ json: Schema.string().describe('JSON string of arguments to pass to the task')
19
19
  })
20
20
 
21
21
  const boundaries = {
@@ -20,7 +20,7 @@ import { Profile } from '../types'
20
20
  import { TaskFingerprintOutput } from '../../utils/taskAnalysis'
21
21
 
22
22
  const schema = new Schema({
23
- descriptorName: Schema.string()
23
+ descriptorName: Schema.string().describe('The task descriptor name to publish (e.g. domain:taskName)')
24
24
  })
25
25
 
26
26
  const boundaries = {
@@ -11,7 +11,7 @@ import { load } from '../conf/load'
11
11
  import { type ForgeConf } from '../types'
12
12
 
13
13
  const schema = new Schema({
14
- descriptorName: Schema.string()
14
+ descriptorName: Schema.string().describe('The task descriptor name to remove (e.g. domain:taskName)')
15
15
  })
16
16
 
17
17
  const boundaries = {
@@ -30,9 +30,9 @@ interface Fixture {
30
30
  const description = 'Replay a task execution from a specified path'
31
31
 
32
32
  const schema = new Schema({
33
- descriptorName: Schema.string(),
34
- path: Schema.string(),
35
- cache: Schema.string().optional()
33
+ descriptorName: Schema.string().describe('The task descriptor name to replay (e.g. domain:taskName)'),
34
+ path: Schema.string().describe('Path to the execution log fixture to replay'),
35
+ cache: Schema.string().describe('Cache mode for boundaries during replay').optional()
36
36
  })
37
37
 
38
38
  const boundaries = {
@@ -21,8 +21,8 @@ import { type ForgeConf, type Profile } from '../types'
21
21
  // For now, we'll use a simple schema without the record type
22
22
  // TODO: Use Schema.record once it's properly built and available
23
23
  const schema = new Schema({
24
- descriptorName: Schema.string(),
25
- args: Schema.mixedRecord()
24
+ descriptorName: Schema.string().describe('The task descriptor name to run (e.g. domain:taskName)'),
25
+ args: Schema.mixedRecord().describe('Arguments to pass to the task')
26
26
  // args will be passed directly without schema validation for now
27
27
  })
28
28
 
@@ -16,8 +16,9 @@ const name = 'sample:newTask'
16
16
  const description = 'Add task description here'
17
17
 
18
18
  const schema = new Schema({
19
- // Add your schema definitions here
20
- // example: myParam: Schema.string()
19
+ // Add your schema definitions here.
20
+ // Use .describe() so the field shows up in \`forge sample:newTask --help\`.
21
+ // example: myParam: Schema.string().describe('What this parameter is for')
21
22
  })
22
23
 
23
24
  const boundaries = {
@@ -9,14 +9,22 @@ interface TaskLocation {
9
9
  interface SchemaProperty {
10
10
  name?: string
11
11
  type: string
12
+ format?: string
13
+ description?: string
14
+ // Internal marker used while building the schema; stripped in favour of the
15
+ // object-level `required` array (JSON Schema semantics) before output.
12
16
  optional?: boolean
13
17
  default?: string
14
18
  properties?: Record<string, SchemaProperty>
19
+ additionalProperties?: SchemaProperty | { anyOf: SchemaProperty[] }
15
20
  }
16
21
 
22
+ // JSON Schema (draft 2020-12) representation of the task input, matching what
23
+ // `Schema.describe()` produces at runtime.
17
24
  interface InputSchema {
18
25
  type: string
19
26
  properties: Record<string, SchemaProperty>
27
+ required?: string[]
20
28
  }
21
29
 
22
30
  interface OutputType {
@@ -558,69 +566,110 @@ function analyzeSchemaArg(node: ts.Expression, sourceFile: ts.SourceFile): Input
558
566
  const arg = node.arguments?.[0]
559
567
  if (arg && ts.isObjectLiteralExpression(arg)) {
560
568
  const properties: Record<string, SchemaProperty> = {}
569
+ const required: string[] = []
561
570
  arg.properties.forEach(prop => {
562
571
  if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
563
572
  const propName = prop.name.text
564
573
  const propValue = analyzeSchemaProp(prop.initializer, sourceFile)
574
+ // Optionality is expressed by absence from `required` (JSON Schema)
575
+ const isOptional = propValue.optional === true
576
+ delete propValue.optional
565
577
  properties[propName] = propValue
578
+ if (!isOptional) {
579
+ required.push(propName)
580
+ }
566
581
  }
567
582
  })
568
- return { type: 'object', properties }
583
+ const result: InputSchema = { type: 'object', properties }
584
+ if (required.length > 0) {
585
+ result.required = required
586
+ }
587
+ return result
569
588
  }
570
589
  }
571
590
  return { type: 'object', properties: {} }
572
591
  }
573
592
 
574
- // Enhanced schema property analysis
575
- function analyzeSchemaProp(node: ts.Expression, sourceFile: ts.SourceFile): SchemaProperty {
576
- // Analyze Schema.string(), Schema.number(), etc.
577
- if (ts.isCallExpression(node)) {
578
- if (ts.isPropertyAccessExpression(node.expression) &&
579
- ts.isIdentifier(node.expression.expression) &&
580
- node.expression.expression.text === 'Schema') {
581
-
582
- const methodName = node.expression.name.text
583
- const baseType: SchemaProperty = { type: getSchemaTypeFromMethod(methodName) }
584
-
585
- return baseType
593
+ // Analyze a single schema field, unwrapping the call chain
594
+ // (e.g. Schema.string().describe('...').optional()) into a JSON Schema property.
595
+ function analyzeSchemaProp(node: ts.Expression, _sourceFile: ts.SourceFile): SchemaProperty {
596
+ let optional = false
597
+ let description: string | undefined
598
+ let defaultValue: string | undefined
599
+
600
+ let current: ts.Expression = node
601
+ while (ts.isCallExpression(current) && ts.isPropertyAccessExpression(current.expression)) {
602
+ const methodName = current.expression.name.text
603
+ const inner = current.expression.expression
604
+
605
+ // Base call: Schema.<type>(...)
606
+ if (ts.isIdentifier(inner) && inner.text === 'Schema') {
607
+ const prop = mapSchemaMethod(methodName)
608
+ if (description !== undefined) {
609
+ prop.description = description
610
+ }
611
+ if (defaultValue !== undefined) {
612
+ prop.default = defaultValue
613
+ }
614
+ if (optional) {
615
+ prop.optional = true
616
+ }
617
+ return prop
586
618
  }
587
- }
588
619
 
589
- // Handle chained calls like Schema.number().optional()
590
- if (ts.isCallExpression(node)) {
591
- if (ts.isPropertyAccessExpression(node.expression)) {
592
- const chainedMethod = node.expression.name.text
593
- if (chainedMethod === 'optional') {
594
- // This is a .optional() call, get the base type
595
- const baseCall = node.expression.expression
596
- if (ts.isCallExpression(baseCall)) {
597
- const baseType = analyzeSchemaProp(baseCall, sourceFile)
598
- return { ...baseType, optional: true }
599
- }
600
- } else if (chainedMethod === 'default') {
601
- // This is a .default() call, get the base type
602
- const baseCall = node.expression.expression
603
- if (ts.isCallExpression(baseCall)) {
604
- const baseType = analyzeSchemaProp(baseCall, sourceFile)
605
- const defaultValue = node.arguments[0]?.getText() || 'undefined'
606
- return { ...baseType, default: defaultValue }
607
- }
620
+ // Chained modifiers
621
+ if (methodName === 'optional') {
622
+ optional = true
623
+ } else if (methodName === 'describe') {
624
+ const arg = current.arguments[0]
625
+ if (arg && ts.isStringLiteral(arg)) {
626
+ description = arg.text
608
627
  }
628
+ } else if (methodName === 'default') {
629
+ defaultValue = current.arguments[0]?.getText() ?? 'undefined'
609
630
  }
631
+ // Other modifiers (min/max/regex/...) don't change the JSON Schema type, so
632
+ // we keep unwrapping until we reach the base Schema.<type>() call.
633
+
634
+ current = inner
610
635
  }
611
636
 
612
637
  return { type: 'unknown' }
613
638
  }
614
639
 
615
- function getSchemaTypeFromMethod(methodName: string): string {
616
- const typeMap: Record<string, string> = {
617
- string: 'string',
618
- number: 'number',
619
- boolean: 'boolean',
620
- array: 'array',
621
- object: 'object'
640
+ // Map a Schema.* helper name to its JSON Schema representation, matching what
641
+ // Schema.describe() emits at runtime.
642
+ function mapSchemaMethod(methodName: string): SchemaProperty {
643
+ switch (methodName) {
644
+ case 'string':
645
+ return { type: 'string' }
646
+ case 'number':
647
+ return { type: 'number' }
648
+ case 'boolean':
649
+ return { type: 'boolean' }
650
+ case 'date':
651
+ return { type: 'string', format: 'date-time' }
652
+ case 'email':
653
+ return { type: 'string', format: 'email' }
654
+ case 'uuid':
655
+ return { type: 'string', format: 'uuid' }
656
+ case 'url':
657
+ return { type: 'string', format: 'uri' }
658
+ case 'array':
659
+ return { type: 'array' }
660
+ case 'object':
661
+ return { type: 'object' }
662
+ case 'stringRecord':
663
+ return { type: 'object', additionalProperties: { type: 'string' } }
664
+ case 'numberRecord':
665
+ return { type: 'object', additionalProperties: { type: 'number' } }
666
+ case 'booleanRecord':
667
+ return { type: 'object', additionalProperties: { type: 'boolean' } }
668
+ case 'mixedRecord':
669
+ return { type: 'object', additionalProperties: { anyOf: [{ type: 'string' }, { type: 'number' }, { type: 'boolean' }] } }
670
+ default:
671
+ return { type: 'unknown' }
622
672
  }
623
- return typeMap[methodName] || 'unknown'
624
673
  }
625
674
 
626
675
 
@@ -1 +0,0 @@
1
- {"input":{"filePath":"/Users/danielzavaladlvega/forgehive/forge-mono-repo/apps/sample-project/src/tasks/test/errors.ts"},"boundaries":{"getCwd":[],"loadConf":[],"readFile":[],"writeFile":[],"ensureFingerprintsFolder":[]},"metadata":{"environment":"cli"},"metrics":[],"type":"error","error":"Invalid input on: descriptorName: Required"}