@forgehive/hive-sdk 0.1.4 → 0.1.6
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.
- package/README.md +158 -19
- package/dist/index.d.ts +45 -1
- package/dist/index.js +261 -6
- package/package.json +3 -3
- package/src/index.ts +332 -5
package/README.md
CHANGED
|
@@ -2,15 +2,36 @@
|
|
|
2
2
|
|
|
3
3
|
A TypeScript/JavaScript SDK for interacting with the Forge Hive logging and quality assessment platform.
|
|
4
4
|
|
|
5
|
+
## ⚠️ Deprecation Notices
|
|
6
|
+
|
|
7
|
+
- **`sendLog()` method**: Deprecated. Use `sendLogByName()` or `sendLogByUuid()` instead.
|
|
8
|
+
- **`projectName`-only configuration**: Deprecated. Use `createClientFromForgeConf()` or include `projectUuid` in your configuration.
|
|
9
|
+
|
|
10
|
+
Future versions will require `projectUuid` and remove support for the legacy `sendLog()` method and name-based project identification.
|
|
11
|
+
|
|
5
12
|
## Quick Start
|
|
6
13
|
|
|
7
|
-
Create a client
|
|
14
|
+
Create a client from forge.json (recommended):
|
|
15
|
+
```typescript
|
|
16
|
+
import { createClientFromForgeConf } from '@forgehive/hive-sdk'
|
|
17
|
+
|
|
18
|
+
// Create client from forge.json (recommended)
|
|
19
|
+
const client = createClientFromForgeConf('./forge.json', {
|
|
20
|
+
metadata: {
|
|
21
|
+
environment: 'production',
|
|
22
|
+
version: '1.0.0'
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Or create with explicit configuration:
|
|
8
28
|
```typescript
|
|
9
29
|
import { HiveLogClient } from '@forgehive/hive-sdk'
|
|
10
30
|
|
|
11
|
-
// Create client with explicit configuration
|
|
31
|
+
// Create client with explicit configuration
|
|
12
32
|
const client = new HiveLogClient({
|
|
13
33
|
projectName: 'My Project',
|
|
34
|
+
projectUuid: 'your-project-uuid',
|
|
14
35
|
apiKey: 'your_api_key',
|
|
15
36
|
apiSecret: 'your_api_secret'
|
|
16
37
|
})
|
|
@@ -22,8 +43,8 @@ On your app
|
|
|
22
43
|
// Run a task
|
|
23
44
|
const [res, error, record] = await someTask.safeRun(args)
|
|
24
45
|
|
|
25
|
-
// Send a log
|
|
26
|
-
await client.
|
|
46
|
+
// Send a log using task name (recommended)
|
|
47
|
+
await client.sendLogByName('stock:getPrice', record)
|
|
27
48
|
```
|
|
28
49
|
|
|
29
50
|
## Installation
|
|
@@ -149,17 +170,61 @@ const clientWithConfig = new HiveLogClient(config)
|
|
|
149
170
|
```
|
|
150
171
|
|
|
151
172
|
**Configuration Object:**
|
|
152
|
-
- `projectName`: Name of your project (required)
|
|
173
|
+
- `projectName`: Name of your project (required, ⚠️ deprecated - use `createClientFromForgeConf` instead)
|
|
174
|
+
- `projectUuid`: UUID of your project (recommended for new implementations)
|
|
153
175
|
- `apiKey`: API key (optional, falls back to `HIVE_API_KEY` environment variable)
|
|
154
176
|
- `apiSecret`: API secret (optional, falls back to `HIVE_API_SECRET` environment variable)
|
|
155
177
|
- `host`: Hive instance URL (optional, falls back to `HIVE_HOST` environment variable, then defaults to `https://www.forgehive.cloud`)
|
|
156
178
|
- `metadata`: Base metadata that will be included with every log (optional)
|
|
179
|
+
- `forgeConfigPath`: Path to forge.json file (optional, defaults to './forge.json')
|
|
157
180
|
|
|
158
181
|
**Returns:** `HiveLogClient` - Configured client instance
|
|
159
182
|
|
|
183
|
+
### `createClientFromForgeConf(forgeConfigPath?: string, additionalConfig?: Partial<HiveLogClientConfig>): HiveLogClient` (Recommended)
|
|
184
|
+
|
|
185
|
+
Creates a Hive log client automatically configured from your forge.json file. This is the recommended way to create clients as it automatically loads project name, UUID, and task configurations.
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
import { createClientFromForgeConf } from '@forgehive/hive-sdk'
|
|
189
|
+
|
|
190
|
+
// Use default forge.json path (./forge.json)
|
|
191
|
+
const client = createClientFromForgeConf()
|
|
192
|
+
|
|
193
|
+
// Use custom forge.json path
|
|
194
|
+
const client = createClientFromForgeConf('./config/forge.json')
|
|
195
|
+
|
|
196
|
+
// Use default path with additional config
|
|
197
|
+
const client = createClientFromForgeConf(undefined, {
|
|
198
|
+
metadata: {
|
|
199
|
+
environment: 'production',
|
|
200
|
+
version: '1.0.0'
|
|
201
|
+
}
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
// Use custom path with additional config
|
|
205
|
+
const client = createClientFromForgeConf('./forge.json', {
|
|
206
|
+
apiKey: 'override-key', // Override any forge.json values
|
|
207
|
+
metadata: {
|
|
208
|
+
environment: 'production'
|
|
209
|
+
}
|
|
210
|
+
})
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
**Parameters:**
|
|
214
|
+
- `forgeConfigPath` (optional): Path to forge.json file (defaults to './forge.json')
|
|
215
|
+
- `additionalConfig` (optional): Additional config to override forge.json values
|
|
216
|
+
|
|
217
|
+
**Returns:** `HiveLogClient` - Configured client with project name, UUID, and task mappings from forge.json
|
|
218
|
+
|
|
219
|
+
**Benefits:**
|
|
220
|
+
- Automatically loads project name and UUID from forge.json
|
|
221
|
+
- Enables `sendLogByName()` method for easy task logging
|
|
222
|
+
- Supports task verification with `testConfig()`
|
|
223
|
+
- Reduces configuration boilerplate
|
|
224
|
+
|
|
160
225
|
### `createHiveLogClient(config: HiveLogClientConfig): HiveLogClient`
|
|
161
226
|
|
|
162
|
-
Factory function that creates a new Hive log client instance.
|
|
227
|
+
Factory function that creates a new Hive log client instance with explicit configuration.
|
|
163
228
|
|
|
164
229
|
```typescript
|
|
165
230
|
import { createHiveLogClient } from '@forgehive/hive-sdk'
|
|
@@ -178,6 +243,61 @@ const client = createHiveLogClient({
|
|
|
178
243
|
**Parameters:** Same as `HiveLogClient` constructor
|
|
179
244
|
**Returns:** `HiveLogClient` - Configured client instance
|
|
180
245
|
|
|
246
|
+
### `sendLog(record: ExecutionRecord, metadata?: Metadata): Promise<'success' | 'error' | 'silent' | LogApiSuccess>` ⚠️ DEPRECATED
|
|
247
|
+
|
|
248
|
+
> **⚠️ DEPRECATION WARNING**: `sendLog()` is deprecated and will be removed in a future version. Use `sendLogByName()` or `sendLogByUuid()` instead for better performance and enhanced features.
|
|
249
|
+
|
|
250
|
+
Sends a log entry to Hive using the legacy endpoint. This method still works but lacks the enhanced features of the newer UUID-based endpoints.
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
// DEPRECATED - Use sendLogByName() instead
|
|
254
|
+
const status = await client.sendLog(record, metadata)
|
|
255
|
+
|
|
256
|
+
// RECOMMENDED - Use sendLogByName() for automatic UUID lookup
|
|
257
|
+
const status = await client.sendLogByName('stock:getPrice', record, metadata)
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### `sendLogByName(taskName: string, record: ExecutionRecord, metadata?: Metadata): Promise<'success' | 'error' | 'silent' | LogApiSuccess>` (Recommended)
|
|
261
|
+
|
|
262
|
+
Sends a log entry to Hive using task name for automatic UUID lookup. Requires `projectUuid` to be set and forge.json to be loaded.
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
// Run a task and send log by name
|
|
266
|
+
const [result, error, record] = await myTask.safeRun(args)
|
|
267
|
+
const status = await client.sendLogByName('stock:getPrice', record, {
|
|
268
|
+
environment: 'production',
|
|
269
|
+
requestId: 'req-123'
|
|
270
|
+
})
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### `sendLogByUuid(record: ExecutionRecord, taskUuid: string, metadata?: Metadata): Promise<'success' | 'error' | 'silent' | LogApiSuccess>`
|
|
274
|
+
|
|
275
|
+
Sends a log entry to Hive using explicit task UUID. Requires `projectUuid` to be set in client config.
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
const status = await client.sendLogByUuid(record, 'a45aafe3-8b01-4b58-b15d-9a96274858ee', metadata)
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### `testConfig(): Promise<TestConfigResult>`
|
|
282
|
+
|
|
283
|
+
Tests the client configuration by verifying credentials, project access, and task synchronization.
|
|
284
|
+
|
|
285
|
+
```typescript
|
|
286
|
+
const result = await client.testConfig()
|
|
287
|
+
console.log('Config test:', result)
|
|
288
|
+
// Returns: { success, teamName, userName, projectName, projectExists, tasksVerified, error? }
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### `getConf(): Record<string, unknown>`
|
|
292
|
+
|
|
293
|
+
Returns the client configuration with masked secrets (shows first 4 + last 4 characters).
|
|
294
|
+
|
|
295
|
+
```typescript
|
|
296
|
+
const config = client.getConf()
|
|
297
|
+
console.log('Client config:', config)
|
|
298
|
+
// Returns: { projectName, projectUuid, host, apiKey: 'abcd****wxyz', ... }
|
|
299
|
+
```
|
|
300
|
+
|
|
181
301
|
### `isActive(): boolean`
|
|
182
302
|
|
|
183
303
|
Check if the client is properly initialized with credentials.
|
|
@@ -206,7 +326,7 @@ Sends a log entry to Hive for a specific task with optional metadata. Accepts bo
|
|
|
206
326
|
|
|
207
327
|
```typescript
|
|
208
328
|
// Using a manual log item
|
|
209
|
-
const status = await hiveLogger.
|
|
329
|
+
const status = await hiveLogger.sendLogByName('user-authentication', {
|
|
210
330
|
input: { username: 'john_doe', timestamp: Date.now() },
|
|
211
331
|
output: { success: true, userId: 12345 },
|
|
212
332
|
boundaries: {
|
|
@@ -226,7 +346,7 @@ const status = await hiveLogger.sendLog('user-authentication', {
|
|
|
226
346
|
|
|
227
347
|
// Using a task execution record directly
|
|
228
348
|
const [result, error, record] = await someTask.safeRun(args)
|
|
229
|
-
await hiveLogger.
|
|
349
|
+
await hiveLogger.sendLogByName('task-name', record, {
|
|
230
350
|
environment: 'production'
|
|
231
351
|
})
|
|
232
352
|
|
|
@@ -320,7 +440,7 @@ The Hive SDK supports a flexible metadata system that allows you to attach conte
|
|
|
320
440
|
|
|
321
441
|
Metadata is merged using the following priority order (highest to lowest):
|
|
322
442
|
|
|
323
|
-
1. **
|
|
443
|
+
1. **sendLogByName metadata** - Metadata passed directly to the `sendLogByName` method
|
|
324
444
|
2. **logItem metadata** - Metadata already present in the `logItem` object
|
|
325
445
|
3. **Client base metadata** - Metadata set when creating the client
|
|
326
446
|
|
|
@@ -348,7 +468,7 @@ const logItem = {
|
|
|
348
468
|
}
|
|
349
469
|
|
|
350
470
|
// Send log with additional metadata
|
|
351
|
-
await client.
|
|
471
|
+
await client.sendLogByName('task-name', logItem, {
|
|
352
472
|
requestId: 'req-456',
|
|
353
473
|
version: '1.2.0' // This overrides both logItem and client version
|
|
354
474
|
})
|
|
@@ -358,8 +478,8 @@ await client.sendLog('task-name', logItem, {
|
|
|
358
478
|
// environment: 'production', // from client
|
|
359
479
|
// team: 'backend', // from client
|
|
360
480
|
// sessionId: 'session-123', // from logItem
|
|
361
|
-
// version: '1.2.0', // from
|
|
362
|
-
// requestId: 'req-456' // from
|
|
481
|
+
// version: '1.2.0', // from sendLogByName (highest priority)
|
|
482
|
+
// requestId: 'req-456' // from sendLogByName
|
|
363
483
|
// }
|
|
364
484
|
```
|
|
365
485
|
|
|
@@ -382,7 +502,7 @@ const client = new HiveLogClient({
|
|
|
382
502
|
**Request-specific metadata:**
|
|
383
503
|
```typescript
|
|
384
504
|
app.post('/api/users', async (req, res) => {
|
|
385
|
-
const result = await client.
|
|
505
|
+
const result = await client.sendLogByName('create-user', {
|
|
386
506
|
input: req.body,
|
|
387
507
|
output: newUser
|
|
388
508
|
}, {
|
|
@@ -405,7 +525,7 @@ const logItem = {
|
|
|
405
525
|
}
|
|
406
526
|
}
|
|
407
527
|
|
|
408
|
-
await client.
|
|
528
|
+
await client.sendLogByName('search', logItem)
|
|
409
529
|
```
|
|
410
530
|
|
|
411
531
|
## Types
|
|
@@ -414,14 +534,33 @@ await client.sendLog('search', logItem)
|
|
|
414
534
|
|
|
415
535
|
```typescript
|
|
416
536
|
interface HiveLogClientConfig {
|
|
417
|
-
projectName: string
|
|
537
|
+
projectName: string // ⚠️ DEPRECATED - use createClientFromForgeConf() instead
|
|
538
|
+
projectUuid?: string // Recommended for new implementations
|
|
418
539
|
apiKey?: string
|
|
419
540
|
apiSecret?: string
|
|
420
541
|
host?: string
|
|
421
542
|
metadata?: Metadata
|
|
543
|
+
forgeConfigPath?: string // Path to forge.json file
|
|
422
544
|
}
|
|
423
545
|
```
|
|
424
546
|
|
|
547
|
+
**⚠️ Migration Guide:**
|
|
548
|
+
```typescript
|
|
549
|
+
// OLD (deprecated) - projectName only
|
|
550
|
+
const client = new HiveLogClient({
|
|
551
|
+
projectName: 'My Project'
|
|
552
|
+
})
|
|
553
|
+
|
|
554
|
+
// NEW (recommended) - use createClientFromForgeConf
|
|
555
|
+
const client = createClientFromForgeConf('./forge.json')
|
|
556
|
+
|
|
557
|
+
// NEW (alternative) - explicit projectUuid
|
|
558
|
+
const client = new HiveLogClient({
|
|
559
|
+
projectName: 'My Project',
|
|
560
|
+
projectUuid: 'your-project-uuid'
|
|
561
|
+
})
|
|
562
|
+
```
|
|
563
|
+
|
|
425
564
|
### `LogItemInput` (also exported as `LogItem`)
|
|
426
565
|
|
|
427
566
|
```typescript
|
|
@@ -552,7 +691,7 @@ const hiveLogger = new HiveLogClient({
|
|
|
552
691
|
// No credentials provided - will use environment variables or go silent
|
|
553
692
|
})
|
|
554
693
|
|
|
555
|
-
const status = await hiveLogger.
|
|
694
|
+
const status = await hiveLogger.sendLogByName('task-name', { data: 'example' })
|
|
556
695
|
if (status === 'error') {
|
|
557
696
|
console.error('Network or API error')
|
|
558
697
|
} else if (status === 'silent') {
|
|
@@ -578,9 +717,9 @@ if (hiveLogger.isActive()) {
|
|
|
578
717
|
}
|
|
579
718
|
```
|
|
580
719
|
|
|
581
|
-
**
|
|
720
|
+
**sendLogByName** - Returns status strings (never throws):
|
|
582
721
|
```typescript
|
|
583
|
-
const status = await hiveLogger.
|
|
722
|
+
const status = await hiveLogger.sendLogByName('task', { data: 'test' })
|
|
584
723
|
// Returns: 'success', 'error', or 'silent'
|
|
585
724
|
```
|
|
586
725
|
|
|
@@ -636,7 +775,7 @@ async function main() {
|
|
|
636
775
|
}
|
|
637
776
|
|
|
638
777
|
// Send log with additional high-priority metadata
|
|
639
|
-
const status = await hiveLogger.
|
|
778
|
+
const status = await hiveLogger.sendLogByName('document-search', logData, {
|
|
640
779
|
requestId: 'req-123456',
|
|
641
780
|
userId: 'user-789',
|
|
642
781
|
sessionId: 'sess-abc123'
|
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export interface HiveLogClientConfig {
|
|
|
10
10
|
apiSecret?: string;
|
|
11
11
|
host?: string;
|
|
12
12
|
metadata?: Metadata;
|
|
13
|
+
forgeConfigPath?: string;
|
|
13
14
|
}
|
|
14
15
|
export interface LogApiResponse {
|
|
15
16
|
uuid: string;
|
|
@@ -39,14 +40,34 @@ export declare class HiveLogClient {
|
|
|
39
40
|
private projectUuid;
|
|
40
41
|
private baseMetadata;
|
|
41
42
|
private isInitialized;
|
|
43
|
+
private forgeConfig;
|
|
42
44
|
constructor(config: HiveLogClientConfig);
|
|
43
45
|
isActive(): boolean;
|
|
46
|
+
private maskSecret;
|
|
47
|
+
getConf(): Record<string, unknown>;
|
|
48
|
+
testConfig(): Promise<{
|
|
49
|
+
success: boolean;
|
|
50
|
+
teamName?: string;
|
|
51
|
+
teamUuid?: string;
|
|
52
|
+
userName?: string;
|
|
53
|
+
projectName?: string;
|
|
54
|
+
projectExists?: boolean;
|
|
55
|
+
tasksVerified?: {
|
|
56
|
+
total: number;
|
|
57
|
+
found: number;
|
|
58
|
+
missing: string[];
|
|
59
|
+
};
|
|
60
|
+
error?: string;
|
|
61
|
+
}>;
|
|
44
62
|
private mergeMetadata;
|
|
45
63
|
sendLog(record: ExecutionRecord, metadata?: Metadata): Promise<'success' | 'error' | 'silent' | LogApiSuccess>;
|
|
46
64
|
sendLogByUuid(record: ExecutionRecord, taskUuid: string, metadata?: Metadata): Promise<'success' | 'error' | 'silent' | LogApiSuccess>;
|
|
47
65
|
getListener(): (record: ExecutionRecord) => Promise<void>;
|
|
48
66
|
getLog(taskName: string, uuid: string): Promise<LogApiResult | null>;
|
|
49
67
|
setQuality(taskName: string, uuid: string, quality: Quality): Promise<boolean>;
|
|
68
|
+
private loadForgeConfig;
|
|
69
|
+
private getTaskUUID;
|
|
70
|
+
sendLogByName(taskName: string, record: ExecutionRecord, metadata?: Metadata): Promise<'success' | 'error' | 'silent' | LogApiSuccess>;
|
|
50
71
|
}
|
|
51
72
|
export declare const createHiveLogClient: (config: HiveLogClientConfig) => HiveLogClient;
|
|
52
73
|
export interface HiveClientConfig {
|
|
@@ -69,6 +90,29 @@ export declare class HiveClient {
|
|
|
69
90
|
private host;
|
|
70
91
|
private projectUuid;
|
|
71
92
|
constructor(config: HiveClientConfig);
|
|
72
|
-
|
|
93
|
+
private maskSecret;
|
|
94
|
+
getConf(): Record<string, unknown>;
|
|
95
|
+
testConfig(): Promise<{
|
|
96
|
+
success: boolean;
|
|
97
|
+
teamName?: string;
|
|
98
|
+
teamUuid?: string;
|
|
99
|
+
userName?: string;
|
|
100
|
+
projectName?: string;
|
|
101
|
+
projectExists?: boolean;
|
|
102
|
+
tasksVerified?: {
|
|
103
|
+
total: number;
|
|
104
|
+
found: number;
|
|
105
|
+
missing: string[];
|
|
106
|
+
};
|
|
107
|
+
error?: string;
|
|
108
|
+
}>;
|
|
109
|
+
invoke(taskUuid: string, payload: unknown): Promise<InvokeResult | null>;
|
|
73
110
|
}
|
|
74
111
|
export declare const createHiveClient: (config: HiveClientConfig) => HiveClient;
|
|
112
|
+
/**
|
|
113
|
+
* Create a HiveLogClient from forge.json configuration
|
|
114
|
+
* @param forgeConfigPath Path to forge.json file (defaults to './forge.json')
|
|
115
|
+
* @param additionalConfig Additional config options to override forge.json values
|
|
116
|
+
* @returns HiveLogClient configured from forge.json
|
|
117
|
+
*/
|
|
118
|
+
export declare const createClientFromForgeConf: (forgeConfigPath?: string, additionalConfig?: Partial<HiveLogClientConfig>) => HiveLogClient;
|
package/dist/index.js
CHANGED
|
@@ -3,12 +3,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.createHiveClient = exports.HiveClient = exports.createHiveLogClient = exports.HiveLogClient = void 0;
|
|
6
|
+
exports.createClientFromForgeConf = exports.createHiveClient = exports.HiveClient = exports.createHiveLogClient = exports.HiveLogClient = void 0;
|
|
7
7
|
exports.isApiError = isApiError;
|
|
8
8
|
exports.isInvokeError = isInvokeError;
|
|
9
9
|
const axios_1 = __importDefault(require("axios"));
|
|
10
10
|
const debug_1 = __importDefault(require("debug"));
|
|
11
11
|
const uuid_1 = require("uuid");
|
|
12
|
+
const fs_1 = __importDefault(require("fs"));
|
|
12
13
|
const log = (0, debug_1.default)('hive-sdk');
|
|
13
14
|
// Type guard to check if response is an error
|
|
14
15
|
function isApiError(response) {
|
|
@@ -16,6 +17,7 @@ function isApiError(response) {
|
|
|
16
17
|
}
|
|
17
18
|
class HiveLogClient {
|
|
18
19
|
constructor(config) {
|
|
20
|
+
this.forgeConfig = null;
|
|
19
21
|
const apiKey = config.apiKey || process.env.HIVE_API_KEY;
|
|
20
22
|
const apiSecret = config.apiSecret || process.env.HIVE_API_SECRET;
|
|
21
23
|
const host = config.host || process.env.HIVE_HOST || 'https://www.forgehive.cloud';
|
|
@@ -36,10 +38,117 @@ class HiveLogClient {
|
|
|
36
38
|
this.isInitialized = true;
|
|
37
39
|
log('HiveLogClient initialized for project "%s" with host "%s"', config.projectName, host);
|
|
38
40
|
}
|
|
41
|
+
// Load forge.json - use provided path or default to ./forge.json
|
|
42
|
+
const configPath = config.forgeConfigPath || './forge.json';
|
|
43
|
+
this.loadForgeConfig(configPath);
|
|
39
44
|
}
|
|
40
45
|
isActive() {
|
|
41
46
|
return this.isInitialized;
|
|
42
47
|
}
|
|
48
|
+
maskSecret(secret) {
|
|
49
|
+
if (!secret || secret.length <= 8) {
|
|
50
|
+
return secret ? '****' : 'null';
|
|
51
|
+
}
|
|
52
|
+
const first4 = secret.slice(0, 4);
|
|
53
|
+
const last4 = secret.slice(-4);
|
|
54
|
+
const middle = '*'.repeat(secret.length - 8);
|
|
55
|
+
return `${first4}${middle}${last4}`;
|
|
56
|
+
}
|
|
57
|
+
getConf() {
|
|
58
|
+
return {
|
|
59
|
+
projectName: this.projectName,
|
|
60
|
+
projectUuid: this.projectUuid,
|
|
61
|
+
host: this.host,
|
|
62
|
+
apiKey: this.maskSecret(this.apiKey),
|
|
63
|
+
apiSecret: this.maskSecret(this.apiSecret),
|
|
64
|
+
isInitialized: this.isInitialized,
|
|
65
|
+
baseMetadata: this.baseMetadata,
|
|
66
|
+
forgeConfig: this.forgeConfig
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
async testConfig() {
|
|
70
|
+
if (!this.isInitialized) {
|
|
71
|
+
return {
|
|
72
|
+
success: false,
|
|
73
|
+
error: 'Client not initialized - missing API credentials'
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
// First verify credentials with /api/me
|
|
78
|
+
const meResponse = await axios_1.default.get(`${this.host}/api/me`, {
|
|
79
|
+
headers: {
|
|
80
|
+
'Authorization': `Bearer ${this.apiKey}:${this.apiSecret}`,
|
|
81
|
+
'Content-Type': 'application/json'
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
if (meResponse.status !== 200) {
|
|
85
|
+
const error = `Credential verification failed: HTTP ${meResponse.status}`;
|
|
86
|
+
log('Failed to verify credentials: %s', error);
|
|
87
|
+
return { success: false, error };
|
|
88
|
+
}
|
|
89
|
+
const meData = meResponse.data;
|
|
90
|
+
log('Successfully verified credentials for user "%s" in team "%s"', meData.user?.name, meData.team?.name);
|
|
91
|
+
// Then verify project exists if we have a projectUuid
|
|
92
|
+
let projectExists = false;
|
|
93
|
+
let projectName;
|
|
94
|
+
let tasksVerified;
|
|
95
|
+
if (this.projectUuid) {
|
|
96
|
+
try {
|
|
97
|
+
const projectResponse = await axios_1.default.get(`${this.host}/api/projects/${this.projectUuid}`, {
|
|
98
|
+
headers: {
|
|
99
|
+
'Authorization': `Bearer ${this.apiKey}:${this.apiSecret}`,
|
|
100
|
+
'Content-Type': 'application/json'
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
if (projectResponse.status === 200) {
|
|
104
|
+
projectExists = true;
|
|
105
|
+
projectName = projectResponse.data.project?.projectName;
|
|
106
|
+
log('Successfully verified project "%s" exists with UUID "%s"', projectName, this.projectUuid);
|
|
107
|
+
// Verify tasks if we have forge config
|
|
108
|
+
if (this.forgeConfig && this.forgeConfig.tasks) {
|
|
109
|
+
const localTasks = Object.keys(this.forgeConfig.tasks);
|
|
110
|
+
const remoteTasks = projectResponse.data.project?.tasks || [];
|
|
111
|
+
const remoteTaskUuids = new Set(remoteTasks.map((task) => task.uuid));
|
|
112
|
+
const missing = [];
|
|
113
|
+
let found = 0;
|
|
114
|
+
for (const taskName of localTasks) {
|
|
115
|
+
const taskUuid = this.forgeConfig.tasks[taskName].uuid;
|
|
116
|
+
if (remoteTaskUuids.has(taskUuid)) {
|
|
117
|
+
found++;
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
missing.push(taskName);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
tasksVerified = {
|
|
124
|
+
total: localTasks.length,
|
|
125
|
+
found,
|
|
126
|
+
missing
|
|
127
|
+
};
|
|
128
|
+
log('Task verification: %d/%d tasks found, missing: %s', found, localTasks.length, missing.join(', '));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch (projectError) {
|
|
133
|
+
log('Project verification failed for UUID "%s": %s', this.projectUuid, projectError instanceof Error ? projectError.message : String(projectError));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
success: true,
|
|
138
|
+
teamName: meData.team?.name,
|
|
139
|
+
teamUuid: meData.team?.uuid,
|
|
140
|
+
userName: meData.user?.name,
|
|
141
|
+
projectName,
|
|
142
|
+
projectExists: this.projectUuid ? projectExists : undefined,
|
|
143
|
+
tasksVerified
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
catch (e) {
|
|
147
|
+
const error = e instanceof Error ? e.message : 'Network error';
|
|
148
|
+
log('Error during config test: %s', error);
|
|
149
|
+
return { success: false, error };
|
|
150
|
+
}
|
|
151
|
+
}
|
|
43
152
|
mergeMetadata(record, sendLogMetadata) {
|
|
44
153
|
// Start with base metadata from client
|
|
45
154
|
let finalMetadata = { ...this.baseMetadata };
|
|
@@ -198,6 +307,52 @@ class HiveLogClient {
|
|
|
198
307
|
return false;
|
|
199
308
|
}
|
|
200
309
|
}
|
|
310
|
+
loadForgeConfig(configPath) {
|
|
311
|
+
try {
|
|
312
|
+
if (fs_1.default.existsSync(configPath)) {
|
|
313
|
+
const configContent = fs_1.default.readFileSync(configPath, 'utf8');
|
|
314
|
+
this.forgeConfig = JSON.parse(configContent);
|
|
315
|
+
log('Found forge.json configuration at %s', configPath);
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
log('No forge.json configuration found at %s', configPath);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
catch (error) {
|
|
322
|
+
log('Error loading forge.json: %s', error instanceof Error ? error.message : String(error));
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
getTaskUUID(taskName) {
|
|
326
|
+
if (!this.forgeConfig) {
|
|
327
|
+
log('No forge.json configuration loaded, cannot get UUID for task "%s"', taskName);
|
|
328
|
+
return null;
|
|
329
|
+
}
|
|
330
|
+
const task = this.forgeConfig.tasks[taskName];
|
|
331
|
+
if (!task) {
|
|
332
|
+
log('Task "%s" not found in forge.json configuration', taskName);
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
log('Found UUID "%s" for task "%s"', task.uuid, taskName);
|
|
336
|
+
return task.uuid;
|
|
337
|
+
}
|
|
338
|
+
async sendLogByName(taskName, record, metadata) {
|
|
339
|
+
if (!this.isInitialized) {
|
|
340
|
+
log('Silent mode: Skipping sendLogByName for task "%s" - client not initialized', taskName);
|
|
341
|
+
return 'silent';
|
|
342
|
+
}
|
|
343
|
+
if (!this.projectUuid) {
|
|
344
|
+
log('Error: sendLogByName requires projectUuid to be set in client config');
|
|
345
|
+
return 'error';
|
|
346
|
+
}
|
|
347
|
+
const taskUuid = this.getTaskUUID(taskName);
|
|
348
|
+
if (!taskUuid) {
|
|
349
|
+
log('Error: Cannot find UUID for task "%s" in forge.json', taskName);
|
|
350
|
+
return 'error';
|
|
351
|
+
}
|
|
352
|
+
// Use the existing sendLogByUuid method
|
|
353
|
+
log('Sending log for task "%s" with uuid "%s"', taskName, taskUuid);
|
|
354
|
+
return await this.sendLogByUuid(record, taskUuid, metadata);
|
|
355
|
+
}
|
|
201
356
|
}
|
|
202
357
|
exports.HiveLogClient = HiveLogClient;
|
|
203
358
|
const createHiveLogClient = (config) => {
|
|
@@ -223,10 +378,77 @@ class HiveClient {
|
|
|
223
378
|
this.apiSecret = apiSecret;
|
|
224
379
|
log('HiveClient initialized for project "%s" with host "%s"', config.projectUuid, host);
|
|
225
380
|
}
|
|
226
|
-
|
|
381
|
+
maskSecret(secret) {
|
|
382
|
+
if (secret.length <= 8) {
|
|
383
|
+
return '****';
|
|
384
|
+
}
|
|
385
|
+
const first4 = secret.slice(0, 4);
|
|
386
|
+
const last4 = secret.slice(-4);
|
|
387
|
+
const middle = '*'.repeat(secret.length - 8);
|
|
388
|
+
return `${first4}${middle}${last4}`;
|
|
389
|
+
}
|
|
390
|
+
getConf() {
|
|
391
|
+
return {
|
|
392
|
+
projectUuid: this.projectUuid,
|
|
393
|
+
host: this.host,
|
|
394
|
+
apiKey: this.maskSecret(this.apiKey),
|
|
395
|
+
apiSecret: this.maskSecret(this.apiSecret)
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
async testConfig() {
|
|
227
399
|
try {
|
|
228
|
-
|
|
229
|
-
|
|
400
|
+
// First verify credentials with /api/me
|
|
401
|
+
const meResponse = await axios_1.default.get(`${this.host}/api/me`, {
|
|
402
|
+
headers: {
|
|
403
|
+
'Authorization': `Bearer ${this.apiKey}:${this.apiSecret}`,
|
|
404
|
+
'Content-Type': 'application/json'
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
if (meResponse.status !== 200) {
|
|
408
|
+
const error = `Credential verification failed: HTTP ${meResponse.status}`;
|
|
409
|
+
log('Failed to verify credentials: %s', error);
|
|
410
|
+
return { success: false, error };
|
|
411
|
+
}
|
|
412
|
+
const meData = meResponse.data;
|
|
413
|
+
log('Successfully verified credentials for user "%s" in team "%s"', meData.user?.name, meData.team?.name);
|
|
414
|
+
// Then verify project exists
|
|
415
|
+
let projectExists = false;
|
|
416
|
+
let projectName;
|
|
417
|
+
try {
|
|
418
|
+
const projectResponse = await axios_1.default.get(`${this.host}/api/projects/${this.projectUuid}`, {
|
|
419
|
+
headers: {
|
|
420
|
+
'Authorization': `Bearer ${this.apiKey}:${this.apiSecret}`,
|
|
421
|
+
'Content-Type': 'application/json'
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
if (projectResponse.status === 200) {
|
|
425
|
+
projectExists = true;
|
|
426
|
+
projectName = projectResponse.data.project?.projectName;
|
|
427
|
+
log('Successfully verified project "%s" exists with UUID "%s"', projectName, this.projectUuid);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
catch (projectError) {
|
|
431
|
+
log('Project verification failed for UUID "%s": %s', this.projectUuid, projectError instanceof Error ? projectError.message : String(projectError));
|
|
432
|
+
}
|
|
433
|
+
return {
|
|
434
|
+
success: true,
|
|
435
|
+
teamName: meData.team?.name,
|
|
436
|
+
teamUuid: meData.team?.uuid,
|
|
437
|
+
userName: meData.user?.name,
|
|
438
|
+
projectName,
|
|
439
|
+
projectExists
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
catch (e) {
|
|
443
|
+
const error = e instanceof Error ? e.message : 'Network error';
|
|
444
|
+
log('Error during config test: %s', error);
|
|
445
|
+
return { success: false, error };
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
async invoke(taskUuid, payload) {
|
|
449
|
+
try {
|
|
450
|
+
const invokeUrl = `${this.host}/api/projects/${this.projectUuid}/tasks/${taskUuid}/invoke`;
|
|
451
|
+
log('Invoking task UUID "%s" at %s', taskUuid, invokeUrl);
|
|
230
452
|
const authToken = `${this.apiKey}:${this.apiSecret}`;
|
|
231
453
|
const response = await axios_1.default.post(invokeUrl, {
|
|
232
454
|
payload
|
|
@@ -236,12 +458,12 @@ class HiveClient {
|
|
|
236
458
|
'Content-Type': 'application/json'
|
|
237
459
|
}
|
|
238
460
|
});
|
|
239
|
-
log('Success: Invoked task "%s"',
|
|
461
|
+
log('Success: Invoked task UUID "%s"', taskUuid);
|
|
240
462
|
return response.data;
|
|
241
463
|
}
|
|
242
464
|
catch (e) {
|
|
243
465
|
const error = e;
|
|
244
|
-
log('Error: Failed to invoke task "%s": %s',
|
|
466
|
+
log('Error: Failed to invoke task UUID "%s": %s', taskUuid, error.message);
|
|
245
467
|
// Check if it's an axios error with response data
|
|
246
468
|
if (axios_1.default.isAxiosError(error) && error.response?.data) {
|
|
247
469
|
return error.response.data;
|
|
@@ -256,3 +478,36 @@ const createHiveClient = (config) => {
|
|
|
256
478
|
return new HiveClient(config);
|
|
257
479
|
};
|
|
258
480
|
exports.createHiveClient = createHiveClient;
|
|
481
|
+
/**
|
|
482
|
+
* Create a HiveLogClient from forge.json configuration
|
|
483
|
+
* @param forgeConfigPath Path to forge.json file (defaults to './forge.json')
|
|
484
|
+
* @param additionalConfig Additional config options to override forge.json values
|
|
485
|
+
* @returns HiveLogClient configured from forge.json
|
|
486
|
+
*/
|
|
487
|
+
const createClientFromForgeConf = (forgeConfigPath = './forge.json', additionalConfig = {}) => {
|
|
488
|
+
log('Creating HiveLogClient from forge.json at "%s"', forgeConfigPath);
|
|
489
|
+
let forgeConfig = null;
|
|
490
|
+
try {
|
|
491
|
+
if (fs_1.default.existsSync(forgeConfigPath)) {
|
|
492
|
+
const configContent = fs_1.default.readFileSync(forgeConfigPath, 'utf8');
|
|
493
|
+
forgeConfig = JSON.parse(configContent);
|
|
494
|
+
log('Loaded forge.json configuration from %s', forgeConfigPath);
|
|
495
|
+
}
|
|
496
|
+
else {
|
|
497
|
+
log('No forge.json found at %s', forgeConfigPath);
|
|
498
|
+
throw new Error(`forge.json not found at ${forgeConfigPath}`);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
catch (error) {
|
|
502
|
+
log('Error loading forge.json: %s', error instanceof Error ? error.message : String(error));
|
|
503
|
+
throw error;
|
|
504
|
+
}
|
|
505
|
+
const config = {
|
|
506
|
+
projectName: forgeConfig.project.name,
|
|
507
|
+
projectUuid: forgeConfig.project.uuid,
|
|
508
|
+
forgeConfigPath,
|
|
509
|
+
...additionalConfig // Allow overriding any config values
|
|
510
|
+
};
|
|
511
|
+
return new HiveLogClient(config);
|
|
512
|
+
};
|
|
513
|
+
exports.createClientFromForgeConf = createClientFromForgeConf;
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forgehive/hive-sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"publishConfig": {
|
|
8
8
|
"access": "public",
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"@forgehive/task": "^0.2.
|
|
10
|
+
"@forgehive/task": "^0.2.7"
|
|
11
11
|
}
|
|
12
12
|
},
|
|
13
13
|
"devDependencies": {
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"axios": "^1.8.4",
|
|
25
25
|
"debug": "^4.4.1",
|
|
26
26
|
"uuid": "^11.1.0",
|
|
27
|
-
"@forgehive/task": "0.2.
|
|
27
|
+
"@forgehive/task": "0.2.7"
|
|
28
28
|
},
|
|
29
29
|
"scripts": {
|
|
30
30
|
"build": "tsc",
|
package/src/index.ts
CHANGED
|
@@ -1,10 +1,25 @@
|
|
|
1
1
|
import axios from 'axios'
|
|
2
2
|
import debug from 'debug'
|
|
3
3
|
import { v7 as uuidv7 } from 'uuid'
|
|
4
|
+
import fs from 'fs'
|
|
4
5
|
import type { ExecutionRecord } from '@forgehive/task'
|
|
5
6
|
|
|
6
7
|
const log = debug('hive-sdk')
|
|
7
8
|
|
|
9
|
+
interface ForgeConfig {
|
|
10
|
+
project: {
|
|
11
|
+
name: string
|
|
12
|
+
uuid: string
|
|
13
|
+
}
|
|
14
|
+
tasks: {
|
|
15
|
+
[taskName: string]: {
|
|
16
|
+
path: string
|
|
17
|
+
handler: string
|
|
18
|
+
uuid: string
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
8
23
|
// Metadata interface
|
|
9
24
|
export interface Metadata {
|
|
10
25
|
[key: string]: string
|
|
@@ -21,6 +36,7 @@ export interface HiveLogClientConfig {
|
|
|
21
36
|
apiSecret?: string
|
|
22
37
|
host?: string
|
|
23
38
|
metadata?: Metadata
|
|
39
|
+
forgeConfigPath?: string // Optional path to forge.json file
|
|
24
40
|
}
|
|
25
41
|
|
|
26
42
|
// API Response Types
|
|
@@ -61,6 +77,7 @@ export class HiveLogClient {
|
|
|
61
77
|
private projectUuid: string | null
|
|
62
78
|
private baseMetadata: Metadata
|
|
63
79
|
private isInitialized: boolean
|
|
80
|
+
private forgeConfig: ForgeConfig | null = null
|
|
64
81
|
|
|
65
82
|
constructor(config: HiveLogClientConfig) {
|
|
66
83
|
const apiKey = config.apiKey || process.env.HIVE_API_KEY
|
|
@@ -84,12 +101,145 @@ export class HiveLogClient {
|
|
|
84
101
|
this.isInitialized = true
|
|
85
102
|
log('HiveLogClient initialized for project "%s" with host "%s"', config.projectName, host)
|
|
86
103
|
}
|
|
104
|
+
|
|
105
|
+
// Load forge.json - use provided path or default to ./forge.json
|
|
106
|
+
const configPath = config.forgeConfigPath || './forge.json'
|
|
107
|
+
this.loadForgeConfig(configPath)
|
|
87
108
|
}
|
|
88
109
|
|
|
89
110
|
isActive(): boolean {
|
|
90
111
|
return this.isInitialized
|
|
91
112
|
}
|
|
92
113
|
|
|
114
|
+
private maskSecret(secret: string | null): string {
|
|
115
|
+
if (!secret || secret.length <= 8) {
|
|
116
|
+
return secret ? '****' : 'null'
|
|
117
|
+
}
|
|
118
|
+
const first4 = secret.slice(0, 4)
|
|
119
|
+
const last4 = secret.slice(-4)
|
|
120
|
+
const middle = '*'.repeat(secret.length - 8)
|
|
121
|
+
return `${first4}${middle}${last4}`
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
getConf(): Record<string, unknown> {
|
|
125
|
+
return {
|
|
126
|
+
projectName: this.projectName,
|
|
127
|
+
projectUuid: this.projectUuid,
|
|
128
|
+
host: this.host,
|
|
129
|
+
apiKey: this.maskSecret(this.apiKey),
|
|
130
|
+
apiSecret: this.maskSecret(this.apiSecret),
|
|
131
|
+
isInitialized: this.isInitialized,
|
|
132
|
+
baseMetadata: this.baseMetadata,
|
|
133
|
+
forgeConfig: this.forgeConfig
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async testConfig(): Promise<{
|
|
138
|
+
success: boolean
|
|
139
|
+
teamName?: string
|
|
140
|
+
teamUuid?: string
|
|
141
|
+
userName?: string
|
|
142
|
+
projectName?: string
|
|
143
|
+
projectExists?: boolean
|
|
144
|
+
tasksVerified?: {
|
|
145
|
+
total: number
|
|
146
|
+
found: number
|
|
147
|
+
missing: string[]
|
|
148
|
+
}
|
|
149
|
+
error?: string
|
|
150
|
+
}> {
|
|
151
|
+
if (!this.isInitialized) {
|
|
152
|
+
return {
|
|
153
|
+
success: false,
|
|
154
|
+
error: 'Client not initialized - missing API credentials'
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
// First verify credentials with /api/me
|
|
160
|
+
const meResponse = await axios.get(`${this.host}/api/me`, {
|
|
161
|
+
headers: {
|
|
162
|
+
'Authorization': `Bearer ${this.apiKey}:${this.apiSecret}`,
|
|
163
|
+
'Content-Type': 'application/json'
|
|
164
|
+
}
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
if (meResponse.status !== 200) {
|
|
168
|
+
const error = `Credential verification failed: HTTP ${meResponse.status}`
|
|
169
|
+
log('Failed to verify credentials: %s', error)
|
|
170
|
+
return { success: false, error }
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const meData = meResponse.data
|
|
174
|
+
log('Successfully verified credentials for user "%s" in team "%s"', meData.user?.name, meData.team?.name)
|
|
175
|
+
|
|
176
|
+
// Then verify project exists if we have a projectUuid
|
|
177
|
+
let projectExists = false
|
|
178
|
+
let projectName: string | undefined
|
|
179
|
+
let tasksVerified: { total: number; found: number; missing: string[] } | undefined
|
|
180
|
+
|
|
181
|
+
if (this.projectUuid) {
|
|
182
|
+
try {
|
|
183
|
+
const projectResponse = await axios.get(`${this.host}/api/projects/${this.projectUuid}`, {
|
|
184
|
+
headers: {
|
|
185
|
+
'Authorization': `Bearer ${this.apiKey}:${this.apiSecret}`,
|
|
186
|
+
'Content-Type': 'application/json'
|
|
187
|
+
}
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
if (projectResponse.status === 200) {
|
|
191
|
+
projectExists = true
|
|
192
|
+
projectName = projectResponse.data.project?.projectName
|
|
193
|
+
log('Successfully verified project "%s" exists with UUID "%s"', projectName, this.projectUuid)
|
|
194
|
+
|
|
195
|
+
// Verify tasks if we have forge config
|
|
196
|
+
if (this.forgeConfig && this.forgeConfig.tasks) {
|
|
197
|
+
const localTasks = Object.keys(this.forgeConfig.tasks)
|
|
198
|
+
const remoteTasks = projectResponse.data.project?.tasks || []
|
|
199
|
+
const remoteTaskUuids = new Set(remoteTasks.map((task: { uuid: string }) => task.uuid))
|
|
200
|
+
|
|
201
|
+
const missing: string[] = []
|
|
202
|
+
let found = 0
|
|
203
|
+
|
|
204
|
+
for (const taskName of localTasks) {
|
|
205
|
+
const taskUuid = this.forgeConfig.tasks[taskName].uuid
|
|
206
|
+
if (remoteTaskUuids.has(taskUuid)) {
|
|
207
|
+
found++
|
|
208
|
+
} else {
|
|
209
|
+
missing.push(taskName)
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
tasksVerified = {
|
|
214
|
+
total: localTasks.length,
|
|
215
|
+
found,
|
|
216
|
+
missing
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
log('Task verification: %d/%d tasks found, missing: %s', found, localTasks.length, missing.join(', '))
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
} catch (projectError) {
|
|
223
|
+
log('Project verification failed for UUID "%s": %s', this.projectUuid, projectError instanceof Error ? projectError.message : String(projectError))
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
success: true,
|
|
229
|
+
teamName: meData.team?.name,
|
|
230
|
+
teamUuid: meData.team?.uuid,
|
|
231
|
+
userName: meData.user?.name,
|
|
232
|
+
projectName,
|
|
233
|
+
projectExists: this.projectUuid ? projectExists : undefined,
|
|
234
|
+
tasksVerified
|
|
235
|
+
}
|
|
236
|
+
} catch (e) {
|
|
237
|
+
const error = e instanceof Error ? e.message : 'Network error'
|
|
238
|
+
log('Error during config test: %s', error)
|
|
239
|
+
return { success: false, error }
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
93
243
|
private mergeMetadata(record: ExecutionRecord, sendLogMetadata?: Metadata): Metadata {
|
|
94
244
|
// Start with base metadata from client
|
|
95
245
|
let finalMetadata = { ...this.baseMetadata }
|
|
@@ -280,6 +430,58 @@ export class HiveLogClient {
|
|
|
280
430
|
return false
|
|
281
431
|
}
|
|
282
432
|
}
|
|
433
|
+
|
|
434
|
+
private loadForgeConfig(configPath: string): void {
|
|
435
|
+
try {
|
|
436
|
+
if (fs.existsSync(configPath)) {
|
|
437
|
+
const configContent = fs.readFileSync(configPath, 'utf8')
|
|
438
|
+
this.forgeConfig = JSON.parse(configContent) as ForgeConfig
|
|
439
|
+
log('Found forge.json configuration at %s', configPath)
|
|
440
|
+
} else {
|
|
441
|
+
log('No forge.json configuration found at %s', configPath)
|
|
442
|
+
}
|
|
443
|
+
} catch (error) {
|
|
444
|
+
log('Error loading forge.json: %s', error instanceof Error ? error.message : String(error))
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
private getTaskUUID(taskName: string): string | null {
|
|
449
|
+
if (!this.forgeConfig) {
|
|
450
|
+
log('No forge.json configuration loaded, cannot get UUID for task "%s"', taskName)
|
|
451
|
+
return null
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const task = this.forgeConfig.tasks[taskName]
|
|
455
|
+
if (!task) {
|
|
456
|
+
log('Task "%s" not found in forge.json configuration', taskName)
|
|
457
|
+
return null
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
log('Found UUID "%s" for task "%s"', task.uuid, taskName)
|
|
461
|
+
return task.uuid
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
async sendLogByName(taskName: string, record: ExecutionRecord, metadata?: Metadata): Promise<'success' | 'error' | 'silent' | LogApiSuccess> {
|
|
465
|
+
if (!this.isInitialized) {
|
|
466
|
+
log('Silent mode: Skipping sendLogByName for task "%s" - client not initialized', taskName)
|
|
467
|
+
return 'silent'
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (!this.projectUuid) {
|
|
471
|
+
log('Error: sendLogByName requires projectUuid to be set in client config')
|
|
472
|
+
return 'error'
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const taskUuid = this.getTaskUUID(taskName)
|
|
476
|
+
if (!taskUuid) {
|
|
477
|
+
log('Error: Cannot find UUID for task "%s" in forge.json', taskName)
|
|
478
|
+
return 'error'
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Use the existing sendLogByUuid method
|
|
482
|
+
log('Sending log for task "%s" with uuid "%s"', taskName, taskUuid)
|
|
483
|
+
return await this.sendLogByUuid(record, taskUuid, metadata)
|
|
484
|
+
}
|
|
283
485
|
}
|
|
284
486
|
|
|
285
487
|
export const createHiveLogClient = (config: HiveLogClientConfig): HiveLogClient => {
|
|
@@ -334,10 +536,97 @@ export class HiveClient {
|
|
|
334
536
|
log('HiveClient initialized for project "%s" with host "%s"', config.projectUuid, host)
|
|
335
537
|
}
|
|
336
538
|
|
|
337
|
-
|
|
539
|
+
private maskSecret(secret: string): string {
|
|
540
|
+
if (secret.length <= 8) {
|
|
541
|
+
return '****'
|
|
542
|
+
}
|
|
543
|
+
const first4 = secret.slice(0, 4)
|
|
544
|
+
const last4 = secret.slice(-4)
|
|
545
|
+
const middle = '*'.repeat(secret.length - 8)
|
|
546
|
+
return `${first4}${middle}${last4}`
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
getConf(): Record<string, unknown> {
|
|
550
|
+
return {
|
|
551
|
+
projectUuid: this.projectUuid,
|
|
552
|
+
host: this.host,
|
|
553
|
+
apiKey: this.maskSecret(this.apiKey),
|
|
554
|
+
apiSecret: this.maskSecret(this.apiSecret)
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
async testConfig(): Promise<{
|
|
559
|
+
success: boolean
|
|
560
|
+
teamName?: string
|
|
561
|
+
teamUuid?: string
|
|
562
|
+
userName?: string
|
|
563
|
+
projectName?: string
|
|
564
|
+
projectExists?: boolean
|
|
565
|
+
tasksVerified?: {
|
|
566
|
+
total: number
|
|
567
|
+
found: number
|
|
568
|
+
missing: string[]
|
|
569
|
+
}
|
|
570
|
+
error?: string
|
|
571
|
+
}> {
|
|
338
572
|
try {
|
|
339
|
-
|
|
340
|
-
|
|
573
|
+
// First verify credentials with /api/me
|
|
574
|
+
const meResponse = await axios.get(`${this.host}/api/me`, {
|
|
575
|
+
headers: {
|
|
576
|
+
'Authorization': `Bearer ${this.apiKey}:${this.apiSecret}`,
|
|
577
|
+
'Content-Type': 'application/json'
|
|
578
|
+
}
|
|
579
|
+
})
|
|
580
|
+
|
|
581
|
+
if (meResponse.status !== 200) {
|
|
582
|
+
const error = `Credential verification failed: HTTP ${meResponse.status}`
|
|
583
|
+
log('Failed to verify credentials: %s', error)
|
|
584
|
+
return { success: false, error }
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
const meData = meResponse.data
|
|
588
|
+
log('Successfully verified credentials for user "%s" in team "%s"', meData.user?.name, meData.team?.name)
|
|
589
|
+
|
|
590
|
+
// Then verify project exists
|
|
591
|
+
let projectExists = false
|
|
592
|
+
let projectName: string | undefined
|
|
593
|
+
|
|
594
|
+
try {
|
|
595
|
+
const projectResponse = await axios.get(`${this.host}/api/projects/${this.projectUuid}`, {
|
|
596
|
+
headers: {
|
|
597
|
+
'Authorization': `Bearer ${this.apiKey}:${this.apiSecret}`,
|
|
598
|
+
'Content-Type': 'application/json'
|
|
599
|
+
}
|
|
600
|
+
})
|
|
601
|
+
|
|
602
|
+
if (projectResponse.status === 200) {
|
|
603
|
+
projectExists = true
|
|
604
|
+
projectName = projectResponse.data.project?.projectName
|
|
605
|
+
log('Successfully verified project "%s" exists with UUID "%s"', projectName, this.projectUuid)
|
|
606
|
+
}
|
|
607
|
+
} catch (projectError) {
|
|
608
|
+
log('Project verification failed for UUID "%s": %s', this.projectUuid, projectError instanceof Error ? projectError.message : String(projectError))
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
return {
|
|
612
|
+
success: true,
|
|
613
|
+
teamName: meData.team?.name,
|
|
614
|
+
teamUuid: meData.team?.uuid,
|
|
615
|
+
userName: meData.user?.name,
|
|
616
|
+
projectName,
|
|
617
|
+
projectExists
|
|
618
|
+
}
|
|
619
|
+
} catch (e) {
|
|
620
|
+
const error = e instanceof Error ? e.message : 'Network error'
|
|
621
|
+
log('Error during config test: %s', error)
|
|
622
|
+
return { success: false, error }
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
async invoke(taskUuid: string, payload: unknown): Promise<InvokeResult | null> {
|
|
627
|
+
try {
|
|
628
|
+
const invokeUrl = `${this.host}/api/projects/${this.projectUuid}/tasks/${taskUuid}/invoke`
|
|
629
|
+
log('Invoking task UUID "%s" at %s', taskUuid, invokeUrl)
|
|
341
630
|
|
|
342
631
|
const authToken = `${this.apiKey}:${this.apiSecret}`
|
|
343
632
|
|
|
@@ -350,11 +639,11 @@ export class HiveClient {
|
|
|
350
639
|
}
|
|
351
640
|
})
|
|
352
641
|
|
|
353
|
-
log('Success: Invoked task "%s"',
|
|
642
|
+
log('Success: Invoked task UUID "%s"', taskUuid)
|
|
354
643
|
return response.data as InvokeResult
|
|
355
644
|
} catch (e) {
|
|
356
645
|
const error = e as Error
|
|
357
|
-
log('Error: Failed to invoke task "%s": %s',
|
|
646
|
+
log('Error: Failed to invoke task UUID "%s": %s', taskUuid, error.message)
|
|
358
647
|
|
|
359
648
|
// Check if it's an axios error with response data
|
|
360
649
|
if (axios.isAxiosError(error) && error.response?.data) {
|
|
@@ -370,3 +659,41 @@ export const createHiveClient = (config: HiveClientConfig): HiveClient => {
|
|
|
370
659
|
log('Creating HiveClient for project "%s"', config.projectUuid)
|
|
371
660
|
return new HiveClient(config)
|
|
372
661
|
}
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* Create a HiveLogClient from forge.json configuration
|
|
665
|
+
* @param forgeConfigPath Path to forge.json file (defaults to './forge.json')
|
|
666
|
+
* @param additionalConfig Additional config options to override forge.json values
|
|
667
|
+
* @returns HiveLogClient configured from forge.json
|
|
668
|
+
*/
|
|
669
|
+
export const createClientFromForgeConf = (
|
|
670
|
+
forgeConfigPath: string = './forge.json',
|
|
671
|
+
additionalConfig: Partial<HiveLogClientConfig> = {}
|
|
672
|
+
): HiveLogClient => {
|
|
673
|
+
log('Creating HiveLogClient from forge.json at "%s"', forgeConfigPath)
|
|
674
|
+
|
|
675
|
+
let forgeConfig: ForgeConfig | null = null
|
|
676
|
+
|
|
677
|
+
try {
|
|
678
|
+
if (fs.existsSync(forgeConfigPath)) {
|
|
679
|
+
const configContent = fs.readFileSync(forgeConfigPath, 'utf8')
|
|
680
|
+
forgeConfig = JSON.parse(configContent) as ForgeConfig
|
|
681
|
+
log('Loaded forge.json configuration from %s', forgeConfigPath)
|
|
682
|
+
} else {
|
|
683
|
+
log('No forge.json found at %s', forgeConfigPath)
|
|
684
|
+
throw new Error(`forge.json not found at ${forgeConfigPath}`)
|
|
685
|
+
}
|
|
686
|
+
} catch (error) {
|
|
687
|
+
log('Error loading forge.json: %s', error instanceof Error ? error.message : String(error))
|
|
688
|
+
throw error
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
const config: HiveLogClientConfig = {
|
|
692
|
+
projectName: forgeConfig.project.name,
|
|
693
|
+
projectUuid: forgeConfig.project.uuid,
|
|
694
|
+
forgeConfigPath,
|
|
695
|
+
...additionalConfig // Allow overriding any config values
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
return new HiveLogClient(config)
|
|
699
|
+
}
|