@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 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 with:
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 (recommended)
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.sendLog('task-name', record)
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.sendLog('user-authentication', {
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.sendLog('task-name', record, {
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. **sendLog metadata** - Metadata passed directly to the `sendLog` method
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.sendLog('task-name', logItem, {
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 sendLog (highest priority)
362
- // requestId: 'req-456' // from sendLog
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.sendLog('create-user', {
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.sendLog('search', logItem)
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.sendLog('task-name', { data: 'example' })
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
- **sendLog** - Returns status strings (never throws):
720
+ **sendLogByName** - Returns status strings (never throws):
582
721
  ```typescript
583
- const status = await hiveLogger.sendLog('task', { data: 'test' })
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.sendLog('document-search', logData, {
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
- invoke(taskName: string, payload: unknown): Promise<InvokeResult | null>;
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
- async invoke(taskName, payload) {
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
- const invokeUrl = `${this.host}/api/project/${this.projectUuid}/task/${taskName}/invoke`;
229
- log('Invoking task "%s" at %s', taskName, invokeUrl);
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"', taskName);
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', taskName, error.message);
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.4",
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.6"
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.6"
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
- async invoke(taskName: string, payload: unknown): Promise<InvokeResult | null> {
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
- const invokeUrl = `${this.host}/api/project/${this.projectUuid}/task/${taskName}/invoke`
340
- log('Invoking task "%s" at %s', taskName, invokeUrl)
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"', taskName)
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', taskName, error.message)
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
+ }