@forgehive/hive-sdk 0.0.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.
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const index_1 = require("../index");
4
+ describe('Hive SDK', () => {
5
+ const originalEnv = process.env;
6
+ beforeEach(() => {
7
+ jest.resetModules();
8
+ process.env = { ...originalEnv };
9
+ });
10
+ afterAll(() => {
11
+ process.env = originalEnv;
12
+ });
13
+ describe('HiveLogClient constructor', () => {
14
+ it('should create client in silent mode when HIVE_API_KEY is missing', () => {
15
+ delete process.env.HIVE_API_KEY;
16
+ process.env.HIVE_API_SECRET = 'test-secret';
17
+ process.env.HIVE_HOST = 'https://test.com';
18
+ expect(() => new index_1.HiveLogClient('test-project')).not.toThrow();
19
+ });
20
+ it('should create client in silent mode when HIVE_API_SECRET is missing', () => {
21
+ process.env.HIVE_API_KEY = 'test-key';
22
+ delete process.env.HIVE_API_SECRET;
23
+ process.env.HIVE_HOST = 'https://test.com';
24
+ expect(() => new index_1.HiveLogClient('test-project')).not.toThrow();
25
+ });
26
+ it('should create client in silent mode when HIVE_HOST is missing', () => {
27
+ process.env.HIVE_API_KEY = 'test-key';
28
+ process.env.HIVE_API_SECRET = 'test-secret';
29
+ delete process.env.HIVE_HOST;
30
+ expect(() => new index_1.HiveLogClient('test-project')).not.toThrow();
31
+ });
32
+ it('should create client in silent mode when all environment variables are missing', () => {
33
+ delete process.env.HIVE_API_KEY;
34
+ delete process.env.HIVE_API_SECRET;
35
+ delete process.env.HIVE_HOST;
36
+ expect(() => new index_1.HiveLogClient('test-project')).not.toThrow();
37
+ });
38
+ it('should create client successfully when all environment variables are present', () => {
39
+ process.env.HIVE_API_KEY = 'test-key';
40
+ process.env.HIVE_API_SECRET = 'test-secret';
41
+ process.env.HIVE_HOST = 'https://test.com';
42
+ expect(() => new index_1.HiveLogClient('test-project')).not.toThrow();
43
+ });
44
+ });
45
+ describe('isActive method', () => {
46
+ it('should return false when credentials are missing', () => {
47
+ delete process.env.HIVE_API_KEY;
48
+ delete process.env.HIVE_API_SECRET;
49
+ delete process.env.HIVE_HOST;
50
+ const client = new index_1.HiveLogClient('test-project');
51
+ expect(client.isActive()).toBe(false);
52
+ });
53
+ it('should return true when all credentials are present', () => {
54
+ process.env.HIVE_API_KEY = 'test-key';
55
+ process.env.HIVE_API_SECRET = 'test-secret';
56
+ process.env.HIVE_HOST = 'https://test.com';
57
+ const client = new index_1.HiveLogClient('test-project');
58
+ expect(client.isActive()).toBe(true);
59
+ });
60
+ });
61
+ describe('createHiveLogClient factory function', () => {
62
+ it('should create HiveLogClient instance when env vars are present', () => {
63
+ process.env.HIVE_API_KEY = 'test-key';
64
+ process.env.HIVE_API_SECRET = 'test-secret';
65
+ process.env.HIVE_HOST = 'https://test.com';
66
+ const client = (0, index_1.createHiveLogClient)('test-project');
67
+ expect(client).toBeInstanceOf(index_1.HiveLogClient);
68
+ });
69
+ it('should create client in silent mode when env vars are missing', () => {
70
+ delete process.env.HIVE_API_KEY;
71
+ delete process.env.HIVE_API_SECRET;
72
+ delete process.env.HIVE_HOST;
73
+ expect(() => (0, index_1.createHiveLogClient)('test-project')).not.toThrow();
74
+ });
75
+ });
76
+ describe('Silent mode behavior', () => {
77
+ let silentClient;
78
+ beforeEach(() => {
79
+ delete process.env.HIVE_API_KEY;
80
+ delete process.env.HIVE_API_SECRET;
81
+ delete process.env.HIVE_HOST;
82
+ silentClient = new index_1.HiveLogClient('silent-project');
83
+ });
84
+ it('should return "silent" for sendLog in silent mode', async () => {
85
+ const result = await silentClient.sendLog('test-task', { data: 'test' });
86
+ expect(result).toBe('silent');
87
+ });
88
+ it('should throw error for getLog in silent mode', async () => {
89
+ await expect(silentClient.getLog('test-task', 'test-uuid')).rejects.toThrow('Missing Hive API credentials or host, get them at https://forgehive.dev');
90
+ });
91
+ it('should throw error for setQuality in silent mode', async () => {
92
+ const quality = { score: 8, reason: 'test', suggestions: 'test' };
93
+ await expect(silentClient.setQuality('test-task', 'test-uuid', quality)).rejects.toThrow('Missing Hive API credentials or host, get them at https://forgehive.dev');
94
+ });
95
+ });
96
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const axios_1 = __importDefault(require("axios"));
7
+ const index_1 = require("../index");
8
+ // Mock axios
9
+ jest.mock('axios');
10
+ const mockedAxios = axios_1.default;
11
+ describe('HiveLogClient sendLog', () => {
12
+ const originalEnv = process.env;
13
+ let client;
14
+ beforeEach(() => {
15
+ jest.resetModules();
16
+ process.env = { ...originalEnv };
17
+ // Set up environment variables
18
+ process.env.HIVE_API_KEY = 'test-api-key';
19
+ process.env.HIVE_API_SECRET = 'test-api-secret';
20
+ process.env.HIVE_HOST = 'https://test-host.com';
21
+ // Create client instance
22
+ client = new index_1.HiveLogClient('test-project');
23
+ // Clear all mocks
24
+ jest.clearAllMocks();
25
+ });
26
+ afterAll(() => {
27
+ process.env = originalEnv;
28
+ });
29
+ describe('successful sendLog', () => {
30
+ it('should send log successfully and return true', async () => {
31
+ // Mock successful axios response
32
+ mockedAxios.post.mockResolvedValueOnce({ data: { success: true } });
33
+ const logItem = { input: 'test-input', output: 'test-output' };
34
+ const result = await client.sendLog('test-task', logItem);
35
+ expect(result).toBe('success');
36
+ expect(mockedAxios.post).toHaveBeenCalledTimes(1);
37
+ expect(mockedAxios.post).toHaveBeenCalledWith('https://test-host.com/api/tasks/log-ingest', {
38
+ projectName: 'test-project',
39
+ taskName: 'test-task',
40
+ logItem: JSON.stringify(logItem)
41
+ }, {
42
+ headers: {
43
+ Authorization: 'Bearer test-api-key:test-api-secret',
44
+ 'Content-Type': 'application/json'
45
+ }
46
+ });
47
+ });
48
+ it('should handle complex log items', async () => {
49
+ mockedAxios.post.mockResolvedValueOnce({ data: { success: true } });
50
+ const complexLogItem = {
51
+ input: { userId: 123, action: 'login' },
52
+ output: { success: true, sessionId: 'abc123' },
53
+ error: null,
54
+ boundaries: {
55
+ database: [{ input: 'SELECT * FROM users', output: [{ id: 123 }], error: null }]
56
+ }
57
+ };
58
+ const result = await client.sendLog('complex-task', complexLogItem);
59
+ expect(result).toBe('success');
60
+ expect(mockedAxios.post).toHaveBeenCalledWith('https://test-host.com/api/tasks/log-ingest', {
61
+ projectName: 'test-project',
62
+ taskName: 'complex-task',
63
+ logItem: JSON.stringify(complexLogItem)
64
+ }, {
65
+ headers: {
66
+ Authorization: 'Bearer test-api-key:test-api-secret',
67
+ 'Content-Type': 'application/json'
68
+ }
69
+ });
70
+ });
71
+ });
72
+ describe('failed sendLog', () => {
73
+ it('should return false when axios throws an error', async () => {
74
+ // Mock axios to throw an error
75
+ mockedAxios.post.mockRejectedValueOnce(new Error('Network error'));
76
+ const logItem = { input: 'test-input' };
77
+ const result = await client.sendLog('test-task', logItem);
78
+ expect(result).toBe('error');
79
+ });
80
+ it('should return false when server returns 500', async () => {
81
+ // Mock axios to throw a server error
82
+ const serverError = new Error('Server Error');
83
+ mockedAxios.post.mockRejectedValueOnce(serverError);
84
+ const result = await client.sendLog('test-task', { input: 'test' });
85
+ expect(result).toBe('error');
86
+ });
87
+ });
88
+ describe('sendLog parameters', () => {
89
+ it('should handle empty log items', async () => {
90
+ mockedAxios.post.mockResolvedValueOnce({ data: { success: true } });
91
+ const result = await client.sendLog('empty-task', {});
92
+ expect(result).toBe('success');
93
+ expect(mockedAxios.post).toHaveBeenCalledWith('https://test-host.com/api/tasks/log-ingest', {
94
+ projectName: 'test-project',
95
+ taskName: 'empty-task',
96
+ logItem: JSON.stringify({})
97
+ }, expect.any(Object));
98
+ });
99
+ it('should handle null/undefined values in log items', async () => {
100
+ mockedAxios.post.mockResolvedValueOnce({ data: { success: true } });
101
+ const logItem = { input: null, output: undefined, error: 'some error' };
102
+ const result = await client.sendLog('null-task', logItem);
103
+ expect(result).toBe('success');
104
+ expect(mockedAxios.post).toHaveBeenCalledWith('https://test-host.com/api/tasks/log-ingest', {
105
+ projectName: 'test-project',
106
+ taskName: 'null-task',
107
+ logItem: JSON.stringify(logItem)
108
+ }, expect.any(Object));
109
+ });
110
+ });
111
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,192 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const axios_1 = __importDefault(require("axios"));
7
+ const index_1 = require("../index");
8
+ // Mock axios
9
+ jest.mock('axios');
10
+ const mockedAxios = axios_1.default;
11
+ describe('HiveLogClient setQuality', () => {
12
+ const originalEnv = process.env;
13
+ let client;
14
+ beforeEach(() => {
15
+ jest.resetModules();
16
+ process.env = { ...originalEnv };
17
+ // Set up environment variables
18
+ process.env.HIVE_API_KEY = 'test-api-key';
19
+ process.env.HIVE_API_SECRET = 'test-api-secret';
20
+ process.env.HIVE_HOST = 'https://test-host.com';
21
+ // Create client instance
22
+ client = new index_1.HiveLogClient('test-project');
23
+ // Clear all mocks
24
+ jest.clearAllMocks();
25
+ });
26
+ afterAll(() => {
27
+ process.env = originalEnv;
28
+ });
29
+ describe('successful setQuality', () => {
30
+ it('should set quality successfully and return true', async () => {
31
+ // Mock successful axios response
32
+ mockedAxios.post.mockResolvedValueOnce({ data: { success: true } });
33
+ const quality = {
34
+ score: 8.5,
35
+ reason: 'Good performance with minor improvements needed',
36
+ suggestions: 'Consider optimizing the database query for better performance'
37
+ };
38
+ const result = await client.setQuality('test-task', 'test-uuid-123', quality);
39
+ expect(result).toBe(true);
40
+ expect(mockedAxios.post).toHaveBeenCalledTimes(1);
41
+ expect(mockedAxios.post).toHaveBeenCalledWith('https://test-host.com/api/tasks/test-task/logs/test-uuid-123/set-quality', {
42
+ quality
43
+ }, {
44
+ headers: {
45
+ Authorization: 'Bearer test-api-key:test-api-secret',
46
+ 'Content-Type': 'application/json'
47
+ }
48
+ });
49
+ });
50
+ it('should handle perfect score quality', async () => {
51
+ mockedAxios.post.mockResolvedValueOnce({ data: { success: true } });
52
+ const perfectQuality = {
53
+ score: 10,
54
+ reason: 'Excellent implementation with no issues found',
55
+ suggestions: 'No improvements needed, great work!'
56
+ };
57
+ const result = await client.setQuality('perfect-task', 'perfect-uuid', perfectQuality);
58
+ expect(result).toBe(true);
59
+ expect(mockedAxios.post).toHaveBeenCalledWith('https://test-host.com/api/tasks/perfect-task/logs/perfect-uuid/set-quality', {
60
+ quality: perfectQuality
61
+ }, expect.any(Object));
62
+ });
63
+ it('should handle poor score quality with detailed suggestions', async () => {
64
+ mockedAxios.post.mockResolvedValueOnce({ data: { success: true } });
65
+ const poorQuality = {
66
+ score: 2.0,
67
+ reason: 'Multiple issues found including performance problems and code quality issues',
68
+ suggestions: 'Refactor the main function, add error handling, optimize database queries, and improve variable naming conventions'
69
+ };
70
+ const result = await client.setQuality('poor-task', 'poor-uuid', poorQuality);
71
+ expect(result).toBe(true);
72
+ expect(mockedAxios.post).toHaveBeenCalledWith('https://test-host.com/api/tasks/poor-task/logs/poor-uuid/set-quality', {
73
+ quality: poorQuality
74
+ }, expect.any(Object));
75
+ });
76
+ it('should handle edge case scores', async () => {
77
+ mockedAxios.post.mockResolvedValueOnce({ data: { success: true } });
78
+ const edgeCaseQuality = {
79
+ score: 0,
80
+ reason: 'Critical failure in implementation',
81
+ suggestions: 'Complete rewrite required'
82
+ };
83
+ const result = await client.setQuality('edge-task', 'edge-uuid', edgeCaseQuality);
84
+ expect(result).toBe(true);
85
+ });
86
+ });
87
+ describe('failed setQuality', () => {
88
+ it('should return false when axios throws an error', async () => {
89
+ // Mock axios to throw an error
90
+ mockedAxios.post.mockRejectedValueOnce(new Error('Network error'));
91
+ const quality = {
92
+ score: 7.0,
93
+ reason: 'Test quality',
94
+ suggestions: 'Test suggestions'
95
+ };
96
+ const result = await client.setQuality('test-task', 'test-uuid', quality);
97
+ expect(result).toBe(false);
98
+ });
99
+ it('should return false when server returns 404', async () => {
100
+ // Mock axios to throw a 404 error
101
+ const notFoundError = new Error('Request failed with status code 404');
102
+ mockedAxios.post.mockRejectedValueOnce(notFoundError);
103
+ const quality = {
104
+ score: 5.0,
105
+ reason: 'Not found test',
106
+ suggestions: 'Test suggestions for not found'
107
+ };
108
+ const result = await client.setQuality('non-existent-task', 'non-existent-uuid', quality);
109
+ expect(result).toBe(false);
110
+ });
111
+ it('should return false when server returns 500', async () => {
112
+ // Mock axios to throw a server error
113
+ const serverError = new Error('Internal Server Error');
114
+ mockedAxios.post.mockRejectedValueOnce(serverError);
115
+ const quality = {
116
+ score: 6.0,
117
+ reason: 'Server error test',
118
+ suggestions: 'Test suggestions for server error'
119
+ };
120
+ const result = await client.setQuality('test-task', 'test-uuid', quality);
121
+ expect(result).toBe(false);
122
+ });
123
+ it('should return false when unauthorized', async () => {
124
+ // Mock axios to throw an unauthorized error
125
+ const unauthorizedError = new Error('Request failed with status code 401');
126
+ mockedAxios.post.mockRejectedValueOnce(unauthorizedError);
127
+ const quality = {
128
+ score: 3.0,
129
+ reason: 'Unauthorized test',
130
+ suggestions: 'Check API credentials'
131
+ };
132
+ const result = await client.setQuality('test-task', 'test-uuid', quality);
133
+ expect(result).toBe(false);
134
+ });
135
+ });
136
+ describe('setQuality parameters', () => {
137
+ it('should handle special characters in taskName and uuid', async () => {
138
+ mockedAxios.post.mockResolvedValueOnce({ data: { success: true } });
139
+ const quality = {
140
+ score: 4.5,
141
+ reason: 'Special characters test',
142
+ suggestions: 'Handle special characters properly'
143
+ };
144
+ const result = await client.setQuality('task-with-special-chars-!@#', 'uuid-with-special-chars-!@#', quality);
145
+ expect(result).toBe(true);
146
+ expect(mockedAxios.post).toHaveBeenCalledWith('https://test-host.com/api/tasks/task-with-special-chars-!@#/logs/uuid-with-special-chars-!@#/set-quality', { quality }, expect.any(Object));
147
+ });
148
+ it('should handle decimal scores correctly', async () => {
149
+ mockedAxios.post.mockResolvedValueOnce({ data: { success: true } });
150
+ const quality = {
151
+ score: 7.123456789,
152
+ reason: 'Decimal precision test',
153
+ suggestions: 'Maintain precision in quality scores'
154
+ };
155
+ const result = await client.setQuality('decimal-task', 'decimal-uuid', quality);
156
+ expect(result).toBe(true);
157
+ expect(mockedAxios.post).toHaveBeenCalledWith('https://test-host.com/api/tasks/decimal-task/logs/decimal-uuid/set-quality', { quality }, expect.any(Object));
158
+ });
159
+ it('should handle long reason and suggestions', async () => {
160
+ mockedAxios.post.mockResolvedValueOnce({ data: { success: true } });
161
+ const longReason = 'This is a very long reason that explains in detail what went wrong with the implementation and why the score is what it is. '.repeat(5);
162
+ const longSuggestions = 'Here are detailed suggestions for improvement: 1. Refactor the main function, 2. Add comprehensive error handling, 3. Optimize database queries, 4. Improve code documentation, 5. Add unit tests. '.repeat(3);
163
+ const quality = {
164
+ score: 3.5,
165
+ reason: longReason,
166
+ suggestions: longSuggestions
167
+ };
168
+ const result = await client.setQuality('long-text-task', 'long-text-uuid', quality);
169
+ expect(result).toBe(true);
170
+ expect(mockedAxios.post).toHaveBeenCalledWith('https://test-host.com/api/tasks/long-text-task/logs/long-text-uuid/set-quality', { quality }, expect.any(Object));
171
+ });
172
+ });
173
+ describe('silent mode behavior', () => {
174
+ it('should throw error when credentials are missing', async () => {
175
+ // Create client without credentials
176
+ process.env.HIVE_API_KEY = '';
177
+ process.env.HIVE_API_SECRET = '';
178
+ process.env.HIVE_HOST = '';
179
+ const silentClient = new index_1.HiveLogClient('silent-project');
180
+ const quality = {
181
+ score: 8.0,
182
+ reason: 'Test quality for silent mode',
183
+ suggestions: 'This should not be processed'
184
+ };
185
+ await expect(silentClient.setQuality('test-task', 'test-uuid', quality))
186
+ .rejects
187
+ .toThrow('Missing Hive API credentials or host, get them at https://forgehive.dev');
188
+ // Verify axios was never called
189
+ expect(mockedAxios.post).not.toHaveBeenCalled();
190
+ });
191
+ });
192
+ });
package/jest.config.js ADDED
@@ -0,0 +1,11 @@
1
+ /** @type {import('jest').Config} */
2
+ module.exports = {
3
+ preset: 'ts-jest',
4
+ testEnvironment: 'node',
5
+ roots: ['<rootDir>/src'],
6
+ testMatch: ['**/src/test/**/*.ts', '**/?(*.)+(spec|test).ts'],
7
+ transform: {
8
+ '^.+\\.ts$': 'ts-jest'
9
+ },
10
+ moduleFileExtensions: ['ts', 'js', 'json', 'node']
11
+ }
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@forgehive/hive-sdk",
3
+ "version": "0.0.1",
4
+ "description": "",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "devDependencies": {
8
+ "@types/jest": "^29.5.14",
9
+ "@types/node": "^24.0.3",
10
+ "jest": "^29.7.0",
11
+ "ts-jest": "^29.1.2"
12
+ },
13
+ "keywords": [],
14
+ "author": "",
15
+ "license": "ISC",
16
+ "dependencies": {
17
+ "axios": "^1.8.4",
18
+ "debug": "^4.4.1"
19
+ },
20
+ "scripts": {
21
+ "build": "tsc",
22
+ "test": "jest",
23
+ "test:watch": "jest --watch"
24
+ }
25
+ }
package/src/index.ts ADDED
@@ -0,0 +1,168 @@
1
+ import axios from 'axios'
2
+ import debug from 'debug'
3
+
4
+ const log = debug('hive-sdk')
5
+
6
+ // API Response Types
7
+ export interface LogApiResponse {
8
+ uuid: string
9
+ taskName: string
10
+ projectName: string
11
+ logItem: {
12
+ input: unknown
13
+ output?: unknown
14
+ error?: unknown
15
+ boundaries?: Record<string, Array<{ input: unknown; output: unknown, error: unknown }>>
16
+ }
17
+ replayFrom?: string
18
+ createdAt: string
19
+ }
20
+
21
+ export interface ApiError {
22
+ error: string
23
+ }
24
+
25
+ export interface LogApiSuccess extends LogApiResponse {}
26
+
27
+ export type LogApiResult = LogApiSuccess | ApiError
28
+
29
+ // Quality interface for setQuality method
30
+ export interface Quality {
31
+ score: number
32
+ reason: string
33
+ suggestions: string
34
+ }
35
+
36
+ // Type guard to check if response is an error
37
+ export function isApiError(response: unknown): response is ApiError {
38
+ return response !== null && typeof response === 'object' && 'error' in response
39
+ }
40
+
41
+ export class HiveLogClient {
42
+ private apiKey: string | null
43
+ private apiSecret: string | null
44
+ private host: string | null
45
+ private projectName: string
46
+ private isInitialized: boolean
47
+
48
+ constructor(projectName: string) {
49
+ const apiKey = process.env.HIVE_API_KEY
50
+ const apiSecret = process.env.HIVE_API_SECRET
51
+ const host = process.env.HIVE_HOST
52
+
53
+ this.projectName = projectName
54
+
55
+ if (!apiKey || !apiSecret || !host) {
56
+ this.apiKey = null
57
+ this.apiSecret = null
58
+ this.host = null
59
+ this.isInitialized = false
60
+ log('HiveLogClient in silent mode for project "%s" - missing credentials (get them at https://forgehive.dev)', projectName)
61
+ } else {
62
+ this.apiKey = apiKey
63
+ this.apiSecret = apiSecret
64
+ this.host = host
65
+ this.isInitialized = true
66
+ log('HiveLogClient initialized for project "%s" with host "%s"', projectName, host)
67
+ }
68
+ }
69
+
70
+ isActive(): boolean {
71
+ return this.isInitialized
72
+ }
73
+
74
+ async sendLog(taskName: string, logItem: unknown): Promise<'success' | 'error' | 'silent'> {
75
+ if (!this.isInitialized) {
76
+ log('Silent mode: Skipping sendLog for task "%s" - client not initialized', taskName)
77
+ return 'silent'
78
+ }
79
+
80
+ try {
81
+ const logsUrl = `${this.host}/api/tasks/log-ingest`
82
+ log('Sending log for task "%s" to %s', taskName, logsUrl)
83
+
84
+ const authToken = `${this.apiKey}:${this.apiSecret}`
85
+
86
+ await axios.post(logsUrl, {
87
+ projectName: this.projectName,
88
+ taskName,
89
+ logItem: JSON.stringify(logItem)
90
+ }, {
91
+ headers: {
92
+ Authorization: `Bearer ${authToken}`,
93
+ 'Content-Type': 'application/json'
94
+ }
95
+ })
96
+
97
+ log('Success: Sent log for task "%s"', taskName)
98
+ return 'success'
99
+ } catch (e) {
100
+ const error = e as Error
101
+ log('Error: Failed to send log for task "%s": %s', taskName, error.message)
102
+ return 'error'
103
+ }
104
+ }
105
+
106
+ async getLog(taskName: string, uuid: string): Promise<LogApiResult | null> {
107
+ if (!this.isInitialized) {
108
+ log('Error: getLog for task "%s" with uuid "%s" - missing credentials', taskName, uuid)
109
+ throw new Error('Missing Hive API credentials or host, get them at https://forgehive.dev')
110
+ }
111
+
112
+ try {
113
+ const logUrl = `${this.host}/api/tasks/${taskName}/logs/${uuid}`
114
+ log('Fetching log for task "%s" with uuid "%s" from %s', taskName, uuid, logUrl)
115
+
116
+ const authToken = `${this.apiKey}:${this.apiSecret}`
117
+
118
+ const response = await axios.get(logUrl, {
119
+ headers: {
120
+ Authorization: `Bearer ${authToken}`,
121
+ 'Content-Type': 'application/json'
122
+ }
123
+ })
124
+
125
+ log('Success: Fetched log for task "%s" with uuid "%s"', taskName, uuid)
126
+ return response.data as LogApiResult
127
+ } catch (e) {
128
+ const error = e as Error
129
+ log('Error: Failed to fetch log for task "%s" with uuid "%s": %s', taskName, uuid, error.message)
130
+ return null
131
+ }
132
+ }
133
+
134
+ async setQuality(taskName: string, uuid: string, quality: Quality): Promise<boolean> {
135
+ if (!this.isInitialized) {
136
+ log('Error: setQuality for task "%s" with uuid "%s" - missing credentials', taskName, uuid)
137
+ throw new Error('Missing Hive API credentials or host, get them at https://forgehive.dev')
138
+ }
139
+
140
+ try {
141
+ const qualityUrl = `${this.host}/api/tasks/${taskName}/logs/${uuid}/set-quality`
142
+ log('Setting quality for task "%s" with uuid "%s" (score: %d) to %s', taskName, uuid, quality.score, qualityUrl)
143
+
144
+ const authToken = `${this.apiKey}:${this.apiSecret}`
145
+
146
+ await axios.post(qualityUrl, {
147
+ quality
148
+ }, {
149
+ headers: {
150
+ Authorization: `Bearer ${authToken}`,
151
+ 'Content-Type': 'application/json'
152
+ }
153
+ })
154
+
155
+ log('Success: Set quality for task "%s" with uuid "%s" (score: %d)', taskName, uuid, quality.score)
156
+ return true
157
+ } catch (e) {
158
+ const error = e as Error
159
+ log('Error: Failed to set quality for task "%s" with uuid "%s": %s', taskName, uuid, error.message)
160
+ return false
161
+ }
162
+ }
163
+ }
164
+
165
+ export const createHiveLogClient = (projectName: string): HiveLogClient => {
166
+ log('Creating HiveLogClient for project "%s"', projectName)
167
+ return new HiveLogClient(projectName)
168
+ }