@forgehive/forge-cli 0.3.17 → 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 (96) hide show
  1. package/dist/runner.js +71 -3
  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 +4 -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 +5 -5
  25. package/dist/tasks/project/create.js +21 -25
  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 +7 -7
  29. package/dist/tasks/project/sync.js +7 -3
  30. package/dist/tasks/project/unlink.d.ts +3 -3
  31. package/dist/tasks/runner/bundle.d.ts +2 -2
  32. package/dist/tasks/runner/bundle.js +2 -2
  33. package/dist/tasks/runner/create.d.ts +2 -2
  34. package/dist/tasks/runner/create.js +1 -1
  35. package/dist/tasks/runner/remove.d.ts +2 -2
  36. package/dist/tasks/runner/remove.js +1 -1
  37. package/dist/tasks/task/createTask.d.ts +4 -4
  38. package/dist/tasks/task/createTask.js +3 -2
  39. package/dist/tasks/task/describe.d.ts +2 -2
  40. package/dist/tasks/task/describe.js +1 -1
  41. package/dist/tasks/task/download.d.ts +4 -4
  42. package/dist/tasks/task/download.js +2 -2
  43. package/dist/tasks/task/fingerprint.d.ts +2 -2
  44. package/dist/tasks/task/fingerprint.js +1 -1
  45. package/dist/tasks/task/invoke.d.ts +4 -4
  46. package/dist/tasks/task/invoke.js +2 -2
  47. package/dist/tasks/task/list.d.ts +3 -3
  48. package/dist/tasks/task/publish.d.ts +4 -4
  49. package/dist/tasks/task/publish.js +1 -1
  50. package/dist/tasks/task/remove.d.ts +2 -2
  51. package/dist/tasks/task/remove.js +1 -1
  52. package/dist/tasks/task/replay.d.ts +14 -7
  53. package/dist/tasks/task/replay.js +33 -23
  54. package/dist/tasks/task/run.d.ts +6 -6
  55. package/dist/tasks/task/run.js +18 -23
  56. package/dist/tasks/types.d.ts +1 -0
  57. package/dist/test/tasks/create.test.js +3 -2
  58. package/dist/utils/taskAnalysis.d.ts +6 -0
  59. package/dist/utils/taskAnalysis.js +82 -41
  60. package/package.json +11 -11
  61. package/pnpm-workspace.yaml +2 -0
  62. package/src/runner.ts +80 -3
  63. package/src/tasks/auth/add.ts +4 -4
  64. package/src/tasks/auth/remove.ts +1 -1
  65. package/src/tasks/auth/switch.ts +1 -1
  66. package/src/tasks/bundle/create.ts +2 -2
  67. package/src/tasks/bundle/fingerprint.ts +2 -2
  68. package/src/tasks/bundle/load.ts +1 -1
  69. package/src/tasks/bundle/zip.ts +4 -4
  70. package/src/tasks/docs/download.ts +5 -2
  71. package/src/tasks/fixture/download.ts +1 -1
  72. package/src/tasks/init.ts +1 -1
  73. package/src/tasks/project/create.ts +21 -27
  74. package/src/tasks/project/link.ts +1 -1
  75. package/src/tasks/project/sync.ts +9 -1
  76. package/src/tasks/runner/bundle.ts +2 -2
  77. package/src/tasks/runner/create.ts +1 -1
  78. package/src/tasks/runner/remove.ts +1 -1
  79. package/src/tasks/task/createTask.ts +3 -2
  80. package/src/tasks/task/describe.ts +1 -1
  81. package/src/tasks/task/download.ts +2 -2
  82. package/src/tasks/task/fingerprint.ts +1 -1
  83. package/src/tasks/task/invoke.ts +2 -2
  84. package/src/tasks/task/publish.ts +1 -1
  85. package/src/tasks/task/remove.ts +1 -1
  86. package/src/tasks/task/replay.ts +38 -24
  87. package/src/tasks/task/run.ts +19 -26
  88. package/src/tasks/types.ts +1 -0
  89. package/src/test/tasks/create.test.ts +3 -2
  90. package/src/utils/taskAnalysis.ts +90 -41
  91. package/logs/bundle:fingerprint.log +0 -1
  92. package/logs/task:fingerprint.log +0 -10
  93. package/logs/task:list.log +0 -1
  94. package/logs/test:guidance.log +0 -1
  95. package/logs/test:uuid.log +0 -1
  96. package/logs/test:uuidCheck.log +0 -1
@@ -19,6 +19,7 @@ export interface ForgeConf {
19
19
  project: {
20
20
  name: string;
21
21
  uuid?: string;
22
+ description?: string;
22
23
  };
23
24
  paths: {
24
25
  logs: string;
@@ -19,8 +19,9 @@ const name = 'sample:newTask'
19
19
  const description = 'Add task description here'
20
20
 
21
21
  const schema = new Schema({
22
- // Add your schema definitions here
23
- // example: myParam: Schema.string()
22
+ // Add your schema definitions here.
23
+ // Use .describe() so the field shows up in \`forge sample:newTask --help\`.
24
+ // example: myParam: Schema.string().describe('What this parameter is for')
24
25
  })
25
26
 
26
27
  const boundaries = {
@@ -1,13 +1,19 @@
1
1
  interface SchemaProperty {
2
2
  name?: string;
3
3
  type: string;
4
+ format?: string;
5
+ description?: string;
4
6
  optional?: boolean;
5
7
  default?: string;
6
8
  properties?: Record<string, SchemaProperty>;
9
+ additionalProperties?: SchemaProperty | {
10
+ anyOf: SchemaProperty[];
11
+ };
7
12
  }
8
13
  interface InputSchema {
9
14
  type: string;
10
15
  properties: Record<string, SchemaProperty>;
16
+ required?: string[];
11
17
  }
12
18
  interface OutputType {
13
19
  type: string;
@@ -502,64 +502,105 @@ function analyzeSchemaArg(node, sourceFile) {
502
502
  const arg = node.arguments?.[0];
503
503
  if (arg && ts.isObjectLiteralExpression(arg)) {
504
504
  const properties = {};
505
+ const required = [];
505
506
  arg.properties.forEach(prop => {
506
507
  if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
507
508
  const propName = prop.name.text;
508
509
  const propValue = analyzeSchemaProp(prop.initializer, sourceFile);
510
+ // Optionality is expressed by absence from `required` (JSON Schema)
511
+ const isOptional = propValue.optional === true;
512
+ delete propValue.optional;
509
513
  properties[propName] = propValue;
514
+ if (!isOptional) {
515
+ required.push(propName);
516
+ }
510
517
  }
511
518
  });
512
- return { type: 'object', properties };
519
+ const result = { type: 'object', properties };
520
+ if (required.length > 0) {
521
+ result.required = required;
522
+ }
523
+ return result;
513
524
  }
514
525
  }
515
526
  return { type: 'object', properties: {} };
516
527
  }
517
- // Enhanced schema property analysis
518
- function analyzeSchemaProp(node, sourceFile) {
519
- // Analyze Schema.string(), Schema.number(), etc.
520
- if (ts.isCallExpression(node)) {
521
- if (ts.isPropertyAccessExpression(node.expression) &&
522
- ts.isIdentifier(node.expression.expression) &&
523
- node.expression.expression.text === 'Schema') {
524
- const methodName = node.expression.name.text;
525
- const baseType = { type: getSchemaTypeFromMethod(methodName) };
526
- return baseType;
527
- }
528
- }
529
- // Handle chained calls like Schema.number().optional()
530
- if (ts.isCallExpression(node)) {
531
- if (ts.isPropertyAccessExpression(node.expression)) {
532
- const chainedMethod = node.expression.name.text;
533
- if (chainedMethod === 'optional') {
534
- // This is a .optional() call, get the base type
535
- const baseCall = node.expression.expression;
536
- if (ts.isCallExpression(baseCall)) {
537
- const baseType = analyzeSchemaProp(baseCall, sourceFile);
538
- return { ...baseType, optional: true };
539
- }
528
+ // Analyze a single schema field, unwrapping the call chain
529
+ // (e.g. Schema.string().describe('...').optional()) into a JSON Schema property.
530
+ function analyzeSchemaProp(node, _sourceFile) {
531
+ let optional = false;
532
+ let description;
533
+ let defaultValue;
534
+ let current = node;
535
+ while (ts.isCallExpression(current) && ts.isPropertyAccessExpression(current.expression)) {
536
+ const methodName = current.expression.name.text;
537
+ const inner = current.expression.expression;
538
+ // Base call: Schema.<type>(...)
539
+ if (ts.isIdentifier(inner) && inner.text === 'Schema') {
540
+ const prop = mapSchemaMethod(methodName);
541
+ if (description !== undefined) {
542
+ prop.description = description;
540
543
  }
541
- else if (chainedMethod === 'default') {
542
- // This is a .default() call, get the base type
543
- const baseCall = node.expression.expression;
544
- if (ts.isCallExpression(baseCall)) {
545
- const baseType = analyzeSchemaProp(baseCall, sourceFile);
546
- const defaultValue = node.arguments[0]?.getText() || 'undefined';
547
- return { ...baseType, default: defaultValue };
548
- }
544
+ if (defaultValue !== undefined) {
545
+ prop.default = defaultValue;
546
+ }
547
+ if (optional) {
548
+ prop.optional = true;
549
549
  }
550
+ return prop;
550
551
  }
552
+ // Chained modifiers
553
+ if (methodName === 'optional') {
554
+ optional = true;
555
+ }
556
+ else if (methodName === 'describe') {
557
+ const arg = current.arguments[0];
558
+ if (arg && ts.isStringLiteral(arg)) {
559
+ description = arg.text;
560
+ }
561
+ }
562
+ else if (methodName === 'default') {
563
+ defaultValue = current.arguments[0]?.getText() ?? 'undefined';
564
+ }
565
+ // Other modifiers (min/max/regex/...) don't change the JSON Schema type, so
566
+ // we keep unwrapping until we reach the base Schema.<type>() call.
567
+ current = inner;
551
568
  }
552
569
  return { type: 'unknown' };
553
570
  }
554
- function getSchemaTypeFromMethod(methodName) {
555
- const typeMap = {
556
- string: 'string',
557
- number: 'number',
558
- boolean: 'boolean',
559
- array: 'array',
560
- object: 'object'
561
- };
562
- return typeMap[methodName] || 'unknown';
571
+ // Map a Schema.* helper name to its JSON Schema representation, matching what
572
+ // Schema.describe() emits at runtime.
573
+ function mapSchemaMethod(methodName) {
574
+ switch (methodName) {
575
+ case 'string':
576
+ return { type: 'string' };
577
+ case 'number':
578
+ return { type: 'number' };
579
+ case 'boolean':
580
+ return { type: 'boolean' };
581
+ case 'date':
582
+ return { type: 'string', format: 'date-time' };
583
+ case 'email':
584
+ return { type: 'string', format: 'email' };
585
+ case 'uuid':
586
+ return { type: 'string', format: 'uuid' };
587
+ case 'url':
588
+ return { type: 'string', format: 'uri' };
589
+ case 'array':
590
+ return { type: 'array' };
591
+ case 'object':
592
+ return { type: 'object' };
593
+ case 'stringRecord':
594
+ return { type: 'object', additionalProperties: { type: 'string' } };
595
+ case 'numberRecord':
596
+ return { type: 'object', additionalProperties: { type: 'number' } };
597
+ case 'booleanRecord':
598
+ return { type: 'object', additionalProperties: { type: 'boolean' } };
599
+ case 'mixedRecord':
600
+ return { type: 'object', additionalProperties: { anyOf: [{ type: 'string' }, { type: 'number' }, { type: 'boolean' }] } };
601
+ default:
602
+ return { type: 'unknown' };
603
+ }
563
604
  }
564
605
  // Enhanced boundary analysis that extracts detailed boundary information
565
606
  function analyzeBoundariesWithTypes(node, sourceFile) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forgehive/forge-cli",
3
- "version": "0.3.17",
3
+ "version": "0.5.1",
4
4
  "description": "TypeScript CLI application",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -10,11 +10,11 @@
10
10
  "publishConfig": {
11
11
  "access": "public",
12
12
  "dependencies": {
13
- "@forgehive/hive-sdk": "^0.1.6",
14
- "@forgehive/record-tape": "^0.2.7",
15
- "@forgehive/runner": "^0.2.7",
16
- "@forgehive/schema": "^0.1.4",
17
- "@forgehive/task": "^0.2.7",
13
+ "@forgehive/hive-sdk": "^0.3.0",
14
+ "@forgehive/record-tape": "^0.3.0",
15
+ "@forgehive/runner": "^0.3.0",
16
+ "@forgehive/schema": "^0.2.0",
17
+ "@forgehive/task": "^0.3.0",
18
18
  "esbuild": "^0.25.0",
19
19
  "handlebars": "^4.7.8",
20
20
  "minimist": "^1.2.8",
@@ -30,11 +30,11 @@
30
30
  "minimist": "^1.2.8",
31
31
  "typescript": "^5.3.3",
32
32
  "uuid": "^11.1.0",
33
- "@forgehive/runner": "0.2.7",
34
- "@forgehive/schema": "0.1.4",
35
- "@forgehive/task": "0.2.7",
36
- "@forgehive/record-tape": "0.2.7",
37
- "@forgehive/hive-sdk": "0.1.6"
33
+ "@forgehive/hive-sdk": "0.3.0",
34
+ "@forgehive/record-tape": "0.3.0",
35
+ "@forgehive/task": "0.3.0",
36
+ "@forgehive/schema": "0.2.0",
37
+ "@forgehive/runner": "0.3.0"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@types/archiver": "^6.0.3",
@@ -0,0 +1,2 @@
1
+ overrides:
2
+ '@forgehive/forge-cli': 'link:'
package/src/runner.ts CHANGED
@@ -93,11 +93,88 @@ runner.load('project:link', linkProject)
93
93
  runner.load('project:unlink', unlinkProject)
94
94
  runner.load('project:sync', syncProject)
95
95
 
96
+ function printAllHelp(): void {
97
+ const tasks = runner.describe()
98
+ console.log('Usage: forge <command> [options]')
99
+ console.log('')
100
+ console.log('Commands:')
101
+
102
+ const groups: Record<string, string[]> = {}
103
+ for (const name of Object.keys(tasks).sort()) {
104
+ const group = name.includes(':') ? name.split(':')[0] : name
105
+ if (!groups[group]) {
106
+ groups[group] = []
107
+ }
108
+ groups[group].push(name)
109
+ }
110
+
111
+ for (const [group, commands] of Object.entries(groups)) {
112
+ console.log(`\n ${group}:`)
113
+ for (const cmd of commands) {
114
+ const desc = tasks[cmd].description || ''
115
+ console.log(` ${cmd.padEnd(26)} ${desc}`)
116
+ }
117
+ }
118
+
119
+ console.log('')
120
+ console.log('Run "forge <command> --help" for more information on a specific command.')
121
+ }
122
+
123
+ function printTaskHelp(taskName: string): void {
124
+ const task = runner.getTask(taskName)
125
+ if (!task) {
126
+ return
127
+ }
128
+
129
+ // `describe()` returns JSON Schema: fields live under `properties`, optionality
130
+ // is the absence from `required`, and `description` carries the help text.
131
+ const schema = task.describe()
132
+ const properties = (schema.properties ?? {}) as Record<string, { type?: string; format?: string; description?: string }>
133
+ const required = new Set((schema.required ?? []) as string[])
134
+ const keys = Object.keys(properties)
135
+
136
+ console.log(`Usage: forge ${taskName}${keys.length > 0 ? ' [options]' : ''}`)
137
+ console.log('')
138
+
139
+ const desc = task.getDescription()
140
+ if (desc) {
141
+ console.log(desc)
142
+ console.log('')
143
+ }
144
+
145
+ if (keys.length > 0) {
146
+ console.log('Options:')
147
+ for (const key of keys) {
148
+ const field = properties[key]
149
+ const type = field.format ?? field.type ?? 'unknown'
150
+ const optional = required.has(key) ? '' : ' (optional)'
151
+ const description = field.description ? ` - ${field.description}` : ''
152
+ console.log(` --${key.padEnd(20)} ${`${type}${optional}`.padEnd(20)}${description}`)
153
+ }
154
+ console.log('')
155
+ } else {
156
+ console.log('This command takes no options.')
157
+ console.log('')
158
+ }
159
+ }
160
+
96
161
  // Set handler
97
162
  runner.setHandler(async (data: ParsedArgs): Promise<unknown> => {
98
163
  const parsedArgs = runner.parseArguments(data)
99
164
  const { taskName, action, args } = parsedArgs
100
165
 
166
+ const helpRequested = (args as Record<string, unknown>)?.help === true
167
+
168
+ if (helpRequested || taskName === 'undefined' || !taskName) {
169
+ if (helpRequested && runner.getTask(taskName)) {
170
+ printTaskHelp(taskName)
171
+ } else {
172
+ printAllHelp()
173
+ }
174
+ setTimeout(() => { process.exit(0) }, 100)
175
+ return { silent: true, outcome: 'Success', taskName, result: null }
176
+ }
177
+
101
178
  console.log('===============================================')
102
179
  console.log(`Running: ${taskName} ${action ? action : ''} ${JSON.stringify(args)}`)
103
180
  console.log('===============================================')
@@ -114,7 +191,7 @@ runner.setHandler(async (data: ParsedArgs): Promise<unknown> => {
114
191
  const commandsWithDescriptor = ['task:create', 'task:remove', 'task:publish', 'task:describe', 'task:fingerprint']
115
192
  const commandsWithRunner = ['runner:create', 'runner:remove']
116
193
  const commandsWithoutParams = ['project:unlink', 'project:sync', 'auth:clear']
117
- const silentCommands = ['task:describe', 'task:list', 'auth:list', 'info']
194
+ const silentCommands = ['task:describe', 'task:list', 'auth:list', 'info', 'docs:download']
118
195
 
119
196
  if (commandsWithDescriptor.includes(taskName)) {
120
197
  result = await task.run({ descriptorName: action })
@@ -191,10 +268,10 @@ runner.setHandler(async (data: ParsedArgs): Promise<unknown> => {
191
268
  logs
192
269
  })
193
270
  } else if (taskName === 'project:create') {
194
- const { projectName, description } = args as { projectName?: string, description?: string }
271
+ const { name, description } = args as { name: string, description?: string }
195
272
 
196
273
  result = await task.run({
197
- projectName,
274
+ name,
198
275
  description
199
276
  })
200
277
  } else if (taskName === 'project:link') {
@@ -12,10 +12,10 @@ import { load as loadProfiles } from './load'
12
12
  import { type Profiles } from '../types'
13
13
 
14
14
  const schema = new Schema({
15
- name: Schema.string(),
16
- apiKey: Schema.string(),
17
- apiSecret: Schema.string(),
18
- url: Schema.string()
15
+ name: Schema.string().describe('The name of the profile'),
16
+ apiKey: Schema.string().describe('The API key for the profile'),
17
+ apiSecret: Schema.string().describe('The API secret for the profile'),
18
+ url: Schema.string().describe('The URL for the profile')
19
19
  })
20
20
 
21
21
  const boundaries = {
@@ -12,7 +12,7 @@ import { load as loadProfiles } from './load'
12
12
  import { type Profiles } from '../types'
13
13
 
14
14
  const schema = new Schema({
15
- profileName: Schema.string()
15
+ profileName: Schema.string().describe('The name of the auth profile to remove')
16
16
  })
17
17
 
18
18
  const boundaries = {
@@ -12,7 +12,7 @@ import { load as loadProfiles } from './load'
12
12
  import { type Profiles } from '../types'
13
13
 
14
14
  const schema = new Schema({
15
- profileName: Schema.string()
15
+ profileName: Schema.string().describe('The name of the auth profile to switch to')
16
16
  })
17
17
 
18
18
  const boundaries = {
@@ -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 = {
@@ -92,6 +92,7 @@ export const download = createTask({
92
92
 
93
93
  // Download Hive logging guide if --logs flag is provided
94
94
  if (logs) {
95
+ console.log('===============================================')
95
96
  const logsTargetPath = customPath
96
97
  ? path.join(path.dirname(customPath), 'hive-logging.md')
97
98
  : 'docs/hive-logging.md'
@@ -125,6 +126,8 @@ export const download = createTask({
125
126
  })
126
127
  }
127
128
 
129
+ console.log('===============================================')
130
+
128
131
  return {
129
132
  success: true,
130
133
  downloads: results,
@@ -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 = {
@@ -12,12 +12,12 @@ import { load as loadConf } from '../conf/load'
12
12
  import { loadCurrent as loadCurrentProfile } from '../auth/loadCurrent'
13
13
  import { type ForgeConf, type Profile } from '../types'
14
14
 
15
- const name = 'project:create'
16
- const description = 'Create a new project in ForgeHive'
15
+ const taskName = 'project:create'
16
+ const taskDescription = 'Create a new project in ForgeHive'
17
17
 
18
18
  const schema = new Schema({
19
- projectName: Schema.string().optional(),
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 = {
@@ -40,39 +40,33 @@ const boundaries = {
40
40
  }
41
41
 
42
42
  export const create = createTask({
43
- name,
44
- description,
43
+ name: taskName,
44
+ description: taskDescription,
45
45
  schema,
46
46
  boundaries,
47
47
  fn: async function (argv, { loadConf, loadCurrentProfile, writeFile, createProject }) {
48
- const { projectName: inputProjectName, description } = argv
48
+ const { name: projectName, description } = argv
49
49
 
50
50
  // Load current configuration
51
51
  const conf = await loadConf({})
52
52
 
53
- // Use provided projectName or fall back to forge.json project name
54
- const projectName = inputProjectName || conf.project.name
55
-
56
- if (!projectName) {
57
- throw new Error('Project name is required. Provide --projectName or ensure forge.json has a project name.')
58
- }
59
-
60
53
  // Check if project already has a UUID, generate one if not
61
- let projectUuid = conf.project.uuid
62
- if (!projectUuid) {
63
- projectUuid = uuidv4()
64
-
65
- // Update forge.json with the new UUID
66
- const forgePath = path.join(process.cwd(), 'forge.json')
67
- const updatedConf: ForgeConf = {
68
- ...conf,
69
- project: {
70
- ...conf.project,
71
- uuid: projectUuid
72
- }
54
+ const projectUuid = conf.project.uuid || uuidv4()
55
+
56
+ // Update forge.json with the project name and UUID
57
+ const forgePath = path.join(process.cwd(), 'forge.json')
58
+ const updatedConf: ForgeConf = {
59
+ ...conf,
60
+ project: {
61
+ ...conf.project,
62
+ name: projectName,
63
+ uuid: projectUuid
73
64
  }
65
+ }
74
66
 
75
- await writeFile(forgePath, JSON.stringify(updatedConf, null, 2))
67
+ await writeFile(forgePath, JSON.stringify(updatedConf, null, 2))
68
+ console.log(`Updated forge.json with project name: ${projectName}`)
69
+ if (!conf.project.uuid) {
76
70
  console.log(`Generated and saved project UUID: ${projectUuid}`)
77
71
  }
78
72
 
@@ -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 = {
@@ -26,6 +26,8 @@ const boundaries = {
26
26
  },
27
27
  syncTasksToHive: async (
28
28
  projectUuid: string,
29
+ projectName: string,
30
+ projectDescription: string | undefined,
29
31
  tasks: Array<{ uuid: string; name: string }>,
30
32
  apiKey: string,
31
33
  apiSecret: string,
@@ -57,7 +59,7 @@ const boundaries = {
57
59
  'Content-Type': 'application/json',
58
60
  'Authorization': `Bearer ${apiKey}:${apiSecret}`
59
61
  },
60
- body: JSON.stringify({ tasks })
62
+ body: JSON.stringify({ projectName, description: projectDescription, tasks })
61
63
  })
62
64
 
63
65
  if (response.ok) {
@@ -134,11 +136,17 @@ export const sync = createTask({
134
136
  }
135
137
 
136
138
  console.log(` 📊 Found ${tasksToSync.length} tasks to sync`)
139
+ console.log(` 📝 Project name: ${forge.project.name}`)
140
+ if (forge.project.description) {
141
+ console.log(` 📝 Project description: ${forge.project.description}`)
142
+ }
137
143
 
138
144
  try {
139
145
  const profile = await loadCurrentProfile({})
140
146
  const result = await syncTasksToHive(
141
147
  forge.project.uuid,
148
+ forge.project.name,
149
+ forge.project.description,
142
150
  tasksToSync,
143
151
  profile.apiKey,
144
152
  profile.apiSecret,
@@ -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 = {