@forgehive/forge-cli 0.3.11 → 0.3.13

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.
@@ -8,6 +8,7 @@ import fs from 'fs/promises'
8
8
  import { camelCase } from '../../utils/camelCase'
9
9
 
10
10
  import { load } from '../conf/load'
11
+ import { loadCurrent } from '../auth/loadCurrent'
11
12
  import { type TaskName, type ForgeConf } from '../types'
12
13
 
13
14
  // Define the template content directly in the code
@@ -55,6 +56,7 @@ const schema = new Schema({
55
56
  const boundaries = {
56
57
  // Load boundaries
57
58
  loadConf: load.asBoundary(),
59
+ loadCurrentProfile: loadCurrent.asBoundary(),
58
60
  loadTemplate: async (): Promise<string> => {
59
61
  return TASK_TEMPLATE
60
62
  },
@@ -106,6 +108,40 @@ const boundaries = {
106
108
  persistConf: async (forge: ForgeConf, cwd: string): Promise<void> => {
107
109
  const forgePath = path.join(cwd, 'forge.json')
108
110
  await fs.writeFile(forgePath, JSON.stringify(forge, null, 2))
111
+ },
112
+ createTaskInHive: async (
113
+ projectUuid: string,
114
+ taskUuid: string,
115
+ taskName: string,
116
+ description: string,
117
+ apiKey: string,
118
+ apiSecret: string,
119
+ baseUrl: string
120
+ ): Promise<{ success: boolean; taskUrl?: string; error?: string }> => {
121
+ try {
122
+ const url = `${baseUrl}/api/projects/${projectUuid}/tasks/${taskUuid}`
123
+ const response = await fetch(url, {
124
+ method: 'POST',
125
+ headers: {
126
+ 'Content-Type': 'application/json',
127
+ 'Authorization': `Bearer ${apiKey}:${apiSecret}`
128
+ },
129
+ body: JSON.stringify({
130
+ taskName,
131
+ description
132
+ })
133
+ })
134
+
135
+ if (response.ok) {
136
+ const taskUrl = `${baseUrl}/tasks/${taskUuid}`
137
+ return { success: true, taskUrl }
138
+ } else {
139
+ const errorData = await response.json().catch(() => ({ error: 'Unknown error' }))
140
+ return { success: false, error: errorData.error || `HTTP ${response.status}` }
141
+ }
142
+ } catch (error) {
143
+ return { success: false, error: error instanceof Error ? error.message : 'Network error' }
144
+ }
109
145
  }
110
146
  }
111
147
 
@@ -118,7 +154,9 @@ export const createTaskCommand = createTask({
118
154
  loadConf,
119
155
  persistConf,
120
156
  parseTaskName,
121
- getCwd
157
+ getCwd,
158
+ loadCurrentProfile,
159
+ createTaskInHive
122
160
  }) {
123
161
  const { taskName, fileName, descriptor, dir } = await parseTaskName(descriptorName)
124
162
  const cwd = await getCwd()
@@ -153,14 +191,42 @@ export const createTaskCommand = createTask({
153
191
  forge.tasks = {}
154
192
  }
155
193
 
194
+ const taskUuid = uuidv4()
156
195
  forge.tasks[descriptor] = {
157
196
  path: `${taskPath}/${fileName}`,
158
197
  handler: taskName,
159
- uuid: uuidv4()
198
+ uuid: taskUuid
160
199
  }
161
200
 
162
201
  await persistConf(forge, cwd)
163
202
 
203
+ // Try to create task in Hive if user has profile and project UUID
204
+ if (forge.project.uuid) {
205
+ try {
206
+ const profile = await loadCurrentProfile({})
207
+ const result = await createTaskInHive(
208
+ forge.project.uuid,
209
+ taskUuid,
210
+ descriptor,
211
+ 'Add task description here',
212
+ profile.apiKey,
213
+ profile.apiSecret,
214
+ profile.url
215
+ )
216
+
217
+ if (result.success && result.taskUrl) {
218
+ console.log('\\nāœ… Task created successfully in Hive!')
219
+ console.log(`šŸ”— View your task: ${result.taskUrl}`)
220
+ } else {
221
+ console.log(`\nāš ļø Task created locally but could not sync to Hive: ${result.error}`)
222
+ console.log(`šŸ”— Host: ${profile.url}`)
223
+ }
224
+ } catch (error) {
225
+ // Silently continue if no profile is configured
226
+ console.log('\\nšŸ“ Task created locally. Configure a profile with \'forge auth:add\' to sync with Hive.')
227
+ }
228
+ }
229
+
164
230
  return { taskPath, fileName }
165
231
  }
166
232
  })
@@ -49,10 +49,34 @@ const boundaries = {
49
49
 
50
50
  return buildsPath
51
51
  },
52
- sendLogToAPI: async (profile: Profile, projectName: string, record: ExecutionRecord): Promise<boolean> => {
52
+ sendLogToAPI: async (
53
+ profile: Profile,
54
+ projectName: string,
55
+ record: ExecutionRecord,
56
+ taskUuid?: string,
57
+ projectUuid?: string
58
+ ): Promise<{ success: boolean; logUuid?: string; taskUuid?: string; skipRemoteLog?: boolean }> => {
59
+ // Check if we have required UUIDs for the new endpoint
60
+ if (!projectUuid || !taskUuid) {
61
+ console.log('===============================================')
62
+ console.log('āš ļø Remote logging skipped - missing UUIDs')
63
+ console.log('')
64
+ console.log('To enable remote logging with enhanced features:')
65
+ if (!projectUuid) {
66
+ console.log('• Use "forge project:create" to create a new project, or')
67
+ console.log('• Use "forge project:link" to connect to an existing project')
68
+ }
69
+ if (!taskUuid) {
70
+ console.log('• Use "forge project:sync" to get the task to have UUID')
71
+ }
72
+ console.log('===============================================')
73
+ return { success: true, skipRemoteLog: true }
74
+ }
75
+
53
76
  try {
54
77
  const config = {
55
78
  projectName,
79
+ projectUuid,
56
80
  apiKey: profile.apiKey,
57
81
  apiSecret: profile.apiSecret,
58
82
  host: profile.url,
@@ -62,21 +86,30 @@ const boundaries = {
62
86
  }
63
87
 
64
88
  const client = new HiveLogClient(config)
65
- const result = await client.sendLog(record)
89
+ console.log('Sending execution log to Hive...')
90
+ const result = await client.sendLogByUuid(record, taskUuid)
66
91
 
67
- if (result === 'success') {
92
+ if (result === 'success' || (typeof result === 'object' && 'uuid' in result)) {
68
93
  console.log('===============================================')
69
- console.log('Log sent to API... ', profile.name, profile.url)
70
- return true
94
+ console.log('āœ… Log sent to Hive successfully')
95
+ console.log(` Profile: ${profile.name}`)
96
+ console.log(` Host: ${profile.url}`)
97
+
98
+ if (typeof result === 'object' && result && 'uuid' in result) {
99
+ const logResponse = result as { uuid: string }
100
+ return { success: true, logUuid: logResponse.uuid, taskUuid }
101
+ }
102
+
103
+ return { success: true, taskUuid }
71
104
  } else {
72
- console.error('Failed to send log to API:', profile.url)
73
- return false
105
+ console.error('āŒ Failed to send log to Hive:', profile.url)
106
+ return { success: false }
74
107
  }
75
108
  } catch (e) {
76
- console.error('Failed to send log to API:', profile.url)
109
+ console.error('āŒ Failed to send log to Hive:', profile.url)
77
110
  const error = e as Error
78
111
  console.error('Error:', error.message)
79
- return false
112
+ return { success: false }
80
113
  }
81
114
  }
82
115
  }
@@ -97,6 +130,8 @@ export const run = createTask({
97
130
  const forge: ForgeConf = await loadConf({})
98
131
  const taskDescriptor = forge.tasks[descriptorName as keyof typeof forge.tasks]
99
132
  const projectName = forge.project.name
133
+ const projectUuid = forge.project.uuid
134
+ const taskUuid = taskDescriptor?.uuid
100
135
 
101
136
  if (taskDescriptor === undefined) {
102
137
  throw new Error('Task is not defined on forge.json')
@@ -169,7 +204,11 @@ export const run = createTask({
169
204
 
170
205
  if (profile) {
171
206
  try {
172
- await sendLogToAPI(profile, projectName, logItem)
207
+ const logResult = await sendLogToAPI(profile, projectName, logItem, taskUuid, projectUuid)
208
+
209
+ if (logResult.success && !logResult.skipRemoteLog && taskUuid) {
210
+ console.log(`šŸ”— View execution logs: ${profile.url}/tasks/${taskUuid}?tab=logs`)
211
+ }
173
212
  } catch (e) {
174
213
  console.error('Failed to send log to API:', e)
175
214
  }
@@ -48,6 +48,9 @@ export interface Profile {
48
48
  apiKey: string
49
49
  apiSecret: string
50
50
  url: string
51
+ teamName?: string
52
+ teamUuid?: string
53
+ userName?: string
51
54
  }
52
55
 
53
56
  export interface Profiles {