@marginfront/sdk 0.0.1 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1410 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // src/cli/index.ts
27
+ var import_commander3 = require("commander");
28
+
29
+ // src/cli/commands/verify.ts
30
+ var import_commander = require("commander");
31
+
32
+ // src/utils/http.ts
33
+ var import_axios = __toESM(require("axios"));
34
+
35
+ // src/errors.ts
36
+ var MarginFrontError = class _MarginFrontError extends Error {
37
+ constructor(message, statusCode = 500, code = "MARGINFRONT_ERROR", details, metadata = {}) {
38
+ super(message);
39
+ this.name = "MarginFrontError";
40
+ this.statusCode = statusCode;
41
+ this.code = code;
42
+ this.details = details;
43
+ this.metadata = metadata;
44
+ this.requestId = metadata.requestId;
45
+ Object.setPrototypeOf(this, _MarginFrontError.prototype);
46
+ if (Error.captureStackTrace) {
47
+ Error.captureStackTrace(this, this.constructor);
48
+ }
49
+ }
50
+ };
51
+ var AuthenticationError = class _AuthenticationError extends MarginFrontError {
52
+ constructor(message = "Invalid or missing API key", metadata = {}) {
53
+ super(message, 401, "AUTHENTICATION_ERROR", void 0, metadata);
54
+ this.name = "AuthenticationError";
55
+ Object.setPrototypeOf(this, _AuthenticationError.prototype);
56
+ }
57
+ };
58
+ var AuthorizationError = class _AuthorizationError extends MarginFrontError {
59
+ constructor(message = "You do not have permission to perform this action", metadata = {}) {
60
+ super(message, 403, "AUTHORIZATION_ERROR", void 0, metadata);
61
+ this.name = "AuthorizationError";
62
+ Object.setPrototypeOf(this, _AuthorizationError.prototype);
63
+ }
64
+ };
65
+ var NotFoundError = class _NotFoundError extends MarginFrontError {
66
+ constructor(message = "Resource not found", resourceType, resourceId, metadata = {}) {
67
+ super(message, 404, "NOT_FOUND_ERROR", void 0, metadata);
68
+ this.name = "NotFoundError";
69
+ this.resourceType = resourceType;
70
+ this.resourceId = resourceId;
71
+ Object.setPrototypeOf(this, _NotFoundError.prototype);
72
+ }
73
+ };
74
+ var ValidationError = class _ValidationError extends MarginFrontError {
75
+ constructor(message = "Validation failed", validationErrors, metadata = {}) {
76
+ super(message, 400, "VALIDATION_ERROR", validationErrors, metadata);
77
+ this.name = "ValidationError";
78
+ this.validationErrors = validationErrors;
79
+ Object.setPrototypeOf(this, _ValidationError.prototype);
80
+ }
81
+ };
82
+ var RateLimitError = class _RateLimitError extends MarginFrontError {
83
+ constructor(message = "Rate limit exceeded", retryAfter, metadata = {}) {
84
+ super(message, 429, "RATE_LIMIT_ERROR", void 0, metadata);
85
+ this.name = "RateLimitError";
86
+ this.retryAfter = retryAfter;
87
+ Object.setPrototypeOf(this, _RateLimitError.prototype);
88
+ }
89
+ };
90
+ var ConflictError = class _ConflictError extends MarginFrontError {
91
+ constructor(message = "Resource conflict", metadata = {}) {
92
+ super(message, 409, "CONFLICT_ERROR", void 0, metadata);
93
+ this.name = "ConflictError";
94
+ Object.setPrototypeOf(this, _ConflictError.prototype);
95
+ }
96
+ };
97
+ var NetworkError = class _NetworkError extends MarginFrontError {
98
+ constructor(message = "Network error occurred", originalError, metadata = {}) {
99
+ super(message, 0, "NETWORK_ERROR", void 0, metadata);
100
+ this.name = "NetworkError";
101
+ this.originalError = originalError;
102
+ Object.setPrototypeOf(this, _NetworkError.prototype);
103
+ }
104
+ };
105
+ var TimeoutError = class _TimeoutError extends MarginFrontError {
106
+ constructor(message = "Request timed out", timeoutMs, metadata = {}) {
107
+ super(message, 0, "TIMEOUT_ERROR", void 0, metadata);
108
+ this.name = "TimeoutError";
109
+ this.timeoutMs = timeoutMs;
110
+ Object.setPrototypeOf(this, _TimeoutError.prototype);
111
+ }
112
+ };
113
+ var InternalError = class _InternalError extends MarginFrontError {
114
+ constructor(message = "Internal server error", metadata = {}) {
115
+ super(message, 500, "INTERNAL_ERROR", void 0, metadata);
116
+ this.name = "InternalError";
117
+ Object.setPrototypeOf(this, _InternalError.prototype);
118
+ }
119
+ };
120
+ var InitializationError = class _InitializationError extends MarginFrontError {
121
+ constructor(message = "Failed to initialize the SDK", metadata = {}) {
122
+ super(message, 0, "INITIALIZATION_ERROR", void 0, metadata);
123
+ this.name = "InitializationError";
124
+ Object.setPrototypeOf(this, _InitializationError.prototype);
125
+ }
126
+ };
127
+ var ApiError = class _ApiError extends MarginFrontError {
128
+ constructor(message = "API request failed", statusCode, errorCode, metadata = {}) {
129
+ super(message, statusCode || 500, errorCode || "API_ERROR", void 0, metadata);
130
+ this.name = "ApiError";
131
+ this.errorCode = errorCode;
132
+ Object.setPrototypeOf(this, _ApiError.prototype);
133
+ }
134
+ };
135
+ function createErrorFromResponse(statusCode, message, details, metadata = {}) {
136
+ switch (statusCode) {
137
+ case 400:
138
+ return new ValidationError(message, details, metadata);
139
+ case 401:
140
+ return new AuthenticationError(message, metadata);
141
+ case 403:
142
+ return new AuthorizationError(message, metadata);
143
+ case 404:
144
+ return new NotFoundError(message, void 0, void 0, metadata);
145
+ case 409:
146
+ return new ConflictError(message, metadata);
147
+ case 422:
148
+ return new ValidationError(message, details, metadata);
149
+ case 429:
150
+ const retryAfter = metadata.retryAfter;
151
+ return new RateLimitError(message, retryAfter, metadata);
152
+ case 500:
153
+ case 502:
154
+ case 503:
155
+ case 504:
156
+ return new InternalError(message, metadata);
157
+ default:
158
+ return new ApiError(message, statusCode, "API_ERROR", metadata);
159
+ }
160
+ }
161
+ function parseApiError(error) {
162
+ const err = error;
163
+ const message = err.message || "Unknown error";
164
+ const statusCode = err.response?.status;
165
+ const responseData = err.response?.data || {};
166
+ const requestId = err.config?.headers?.["X-Request-ID"] || "unknown";
167
+ const metadata = {
168
+ requestId,
169
+ url: err.config?.url,
170
+ method: err.config?.method,
171
+ responseData,
172
+ retryAfter: err.response?.headers?.["retry-after"] ? parseInt(err.response.headers["retry-after"], 10) : void 0
173
+ };
174
+ if (!statusCode) {
175
+ if (err.code === "ECONNABORTED") {
176
+ return new TimeoutError(message, void 0, metadata);
177
+ }
178
+ return new NetworkError(message, err, metadata);
179
+ }
180
+ return createErrorFromResponse(
181
+ statusCode,
182
+ responseData.message || responseData.error || message,
183
+ responseData.errors,
184
+ metadata
185
+ );
186
+ }
187
+
188
+ // src/utils/http.ts
189
+ var DEFAULT_BASE_URL = "https://api-qa.costingly.com/v1";
190
+ var DEFAULT_TIMEOUT = 3e4;
191
+ var DEFAULT_RETRIES = 0;
192
+ var DEFAULT_RETRY_DELAY = 300;
193
+ var Logger = class {
194
+ constructor(config) {
195
+ this.levelPriority = {
196
+ debug: 0,
197
+ info: 1,
198
+ warn: 2,
199
+ error: 3
200
+ };
201
+ this.defaultHandler = (level, message, data) => {
202
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
203
+ const logMessage = `[${timestamp}] [MarginFront] [${level.toUpperCase()}] ${message}`;
204
+ const logFn = level === "error" ? console.error : level === "warn" ? console.warn : console.log;
205
+ if (data) {
206
+ logFn(logMessage, data);
207
+ } else {
208
+ logFn(logMessage);
209
+ }
210
+ };
211
+ this.enabled = config.logging?.enabled ?? config.debug ?? false;
212
+ this.minLevel = config.logging?.level ?? "info";
213
+ this.handler = config.logging?.handler ?? this.defaultHandler;
214
+ }
215
+ shouldLog(level) {
216
+ return this.enabled && this.levelPriority[level] >= this.levelPriority[this.minLevel];
217
+ }
218
+ debug(message, data) {
219
+ if (this.shouldLog("debug")) this.handler("debug", message, data);
220
+ }
221
+ info(message, data) {
222
+ if (this.shouldLog("info")) this.handler("info", message, data);
223
+ }
224
+ warn(message, data) {
225
+ if (this.shouldLog("warn")) this.handler("warn", message, data);
226
+ }
227
+ error(message, data) {
228
+ if (this.shouldLog("error")) this.handler("error", message, data);
229
+ }
230
+ };
231
+ var Telemetry = class {
232
+ constructor(config) {
233
+ this.activeRequests = /* @__PURE__ */ new Map();
234
+ this.requestCount = 0;
235
+ this.successCount = 0;
236
+ this.errorCount = 0;
237
+ this.totalDuration = 0;
238
+ this.errorTypes = /* @__PURE__ */ new Map();
239
+ this.enabled = config.telemetry?.enabled ?? false;
240
+ this.sampleRate = config.telemetry?.sampleRate ?? 1;
241
+ this.handler = config.telemetry?.handler ?? (() => {
242
+ });
243
+ }
244
+ shouldCollect() {
245
+ return this.enabled && Math.random() <= this.sampleRate;
246
+ }
247
+ generateRequestId() {
248
+ return `req_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
249
+ }
250
+ normalizePath(path) {
251
+ if (!path) return "";
252
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
253
+ return normalizedPath.replace(/\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, "/:id").replace(/\/\d+(?=\/|$)/g, "/:id");
254
+ }
255
+ startRequest(method, path, requestId) {
256
+ const shouldTrack = this.shouldCollect();
257
+ const id = requestId || this.generateRequestId();
258
+ if (shouldTrack) {
259
+ const metrics = {
260
+ requestId: id,
261
+ method: method.toUpperCase(),
262
+ path: this.normalizePath(path),
263
+ startTime: Date.now()
264
+ };
265
+ this.activeRequests.set(id, metrics);
266
+ }
267
+ return { requestId: id, isTracked: shouldTrack };
268
+ }
269
+ endRequest(requestId, statusCode, error, retryCount) {
270
+ const metrics = this.activeRequests.get(requestId);
271
+ if (!metrics) return;
272
+ metrics.endTime = Date.now();
273
+ metrics.duration = metrics.endTime - metrics.startTime;
274
+ metrics.statusCode = statusCode;
275
+ metrics.success = !error && (statusCode ? statusCode < 400 : true);
276
+ metrics.retryCount = retryCount || 0;
277
+ if (error) {
278
+ metrics.errorMessage = error.message;
279
+ metrics.errorType = error.name || "UnknownError";
280
+ const currentCount = this.errorTypes.get(metrics.errorType) || 0;
281
+ this.errorTypes.set(metrics.errorType, currentCount + 1);
282
+ this.errorCount++;
283
+ } else {
284
+ this.successCount++;
285
+ }
286
+ this.requestCount++;
287
+ this.totalDuration += metrics.duration;
288
+ this.handler(metrics);
289
+ this.activeRequests.delete(requestId);
290
+ }
291
+ getStats() {
292
+ return {
293
+ requestCount: this.requestCount,
294
+ successCount: this.successCount,
295
+ errorCount: this.errorCount,
296
+ successRate: this.requestCount > 0 ? this.successCount / this.requestCount : 1,
297
+ averageDuration: this.requestCount > 0 ? this.totalDuration / this.requestCount : 0,
298
+ errorBreakdown: Object.fromEntries(this.errorTypes)
299
+ };
300
+ }
301
+ resetStats() {
302
+ this.requestCount = 0;
303
+ this.successCount = 0;
304
+ this.errorCount = 0;
305
+ this.totalDuration = 0;
306
+ this.errorTypes.clear();
307
+ }
308
+ };
309
+ function calculateBackoff(retryCount, initialDelay) {
310
+ return initialDelay * Math.pow(2, retryCount);
311
+ }
312
+ function sleep(ms) {
313
+ return new Promise((resolve) => setTimeout(resolve, ms));
314
+ }
315
+ function isRetryable(error) {
316
+ if (error.response) {
317
+ const status = error.response.status;
318
+ return status === 429 || status >= 500 && status < 600;
319
+ }
320
+ return !error.response;
321
+ }
322
+ var HttpClient = class {
323
+ constructor(config) {
324
+ this.logger = new Logger(config);
325
+ this.telemetry = new Telemetry(config);
326
+ this.retries = config.retries ?? DEFAULT_RETRIES;
327
+ this.retryDelay = config.retryDelay ?? DEFAULT_RETRY_DELAY;
328
+ const baseURL = config.baseUrl || config.baseURL || DEFAULT_BASE_URL;
329
+ const isBrowser = typeof globalThis !== "undefined" && typeof globalThis.window !== "undefined";
330
+ const headers = {
331
+ "Content-Type": "application/json",
332
+ Accept: "application/json",
333
+ "x-api-key": config.apiKey,
334
+ ...config.headers
335
+ };
336
+ if (!isBrowser) {
337
+ headers["User-Agent"] = "marginfront-node/1.0.0";
338
+ }
339
+ this.client = import_axios.default.create({
340
+ baseURL,
341
+ timeout: config.timeout || DEFAULT_TIMEOUT,
342
+ headers
343
+ });
344
+ this.client.interceptors.request.use(
345
+ (requestConfig) => {
346
+ const customConfig = requestConfig;
347
+ customConfig.metadata = customConfig.metadata || {};
348
+ const { requestId, isTracked } = this.telemetry.startRequest(
349
+ requestConfig.method || "GET",
350
+ requestConfig.url || "",
351
+ customConfig.metadata.requestId
352
+ );
353
+ requestConfig.headers = requestConfig.headers || {};
354
+ requestConfig.headers["X-Request-ID"] = requestId;
355
+ customConfig.metadata.requestId = requestId;
356
+ customConfig.metadata.telemetryTracked = isTracked;
357
+ this.logger.debug(`Request: ${requestConfig.method?.toUpperCase()} ${requestConfig.url}`, {
358
+ requestId
359
+ });
360
+ return requestConfig;
361
+ },
362
+ (error) => Promise.reject(error)
363
+ );
364
+ this.client.interceptors.response.use(
365
+ (response) => {
366
+ const customConfig = response.config;
367
+ const requestId = customConfig.metadata?.requestId;
368
+ const isTracked = customConfig.metadata?.telemetryTracked;
369
+ if (isTracked && requestId) {
370
+ this.telemetry.endRequest(requestId, response.status);
371
+ }
372
+ this.logger.debug(`Response: ${response.status}`, { requestId });
373
+ return response;
374
+ },
375
+ (error) => {
376
+ const customConfig = error.config;
377
+ const requestId = customConfig?.metadata?.requestId;
378
+ const isTracked = customConfig?.metadata?.telemetryTracked;
379
+ const retryCount = customConfig?.metadata?.retryCount || 0;
380
+ if (isTracked && requestId) {
381
+ this.telemetry.endRequest(requestId, error.response?.status, error, retryCount);
382
+ }
383
+ this.logger.error(`Error: ${error.message}`, {
384
+ requestId,
385
+ status: error.response?.status
386
+ });
387
+ return Promise.reject(error);
388
+ }
389
+ );
390
+ }
391
+ /**
392
+ * Make a request with retry logic
393
+ */
394
+ async request(config) {
395
+ let lastError;
396
+ config.metadata = config.metadata || {};
397
+ for (let retry = 0; retry <= this.retries; retry++) {
398
+ try {
399
+ config.metadata.retryCount = retry;
400
+ if (retry > 0) {
401
+ this.logger.info(`Retry attempt ${retry}/${this.retries}`, {
402
+ requestId: config.metadata.requestId
403
+ });
404
+ }
405
+ const response = await this.client.request(config);
406
+ return response.data;
407
+ } catch (error) {
408
+ lastError = error;
409
+ if (retry < this.retries && isRetryable(lastError)) {
410
+ const retryAfterHeader = lastError.response?.headers?.["retry-after"];
411
+ const backoffTime = retryAfterHeader ? parseInt(retryAfterHeader, 10) * 1e3 : calculateBackoff(retry, this.retryDelay);
412
+ this.logger.debug(`Retrying in ${backoffTime}ms...`);
413
+ await sleep(backoffTime);
414
+ continue;
415
+ }
416
+ break;
417
+ }
418
+ }
419
+ throw parseApiError(lastError);
420
+ }
421
+ /**
422
+ * Make a GET request
423
+ */
424
+ async get(path, params) {
425
+ const config = {
426
+ method: "GET",
427
+ url: path
428
+ };
429
+ if (params) {
430
+ config.params = Object.fromEntries(
431
+ Object.entries(params).filter(([, v]) => v !== void 0 && v !== null)
432
+ );
433
+ }
434
+ return this.request(config);
435
+ }
436
+ /**
437
+ * Make a POST request
438
+ */
439
+ async post(path, data) {
440
+ return this.request({
441
+ method: "POST",
442
+ url: path,
443
+ data
444
+ });
445
+ }
446
+ /**
447
+ * Make a PUT request
448
+ */
449
+ async put(path, data) {
450
+ return this.request({
451
+ method: "PUT",
452
+ url: path,
453
+ data
454
+ });
455
+ }
456
+ /**
457
+ * Make a PATCH request
458
+ */
459
+ async patch(path, data) {
460
+ return this.request({
461
+ method: "PATCH",
462
+ url: path,
463
+ data
464
+ });
465
+ }
466
+ /**
467
+ * Make a DELETE request
468
+ */
469
+ async delete(path) {
470
+ return this.request({
471
+ method: "DELETE",
472
+ url: path
473
+ });
474
+ }
475
+ /**
476
+ * Get telemetry statistics
477
+ */
478
+ getTelemetryStats() {
479
+ return this.telemetry.getStats();
480
+ }
481
+ /**
482
+ * Reset telemetry statistics
483
+ */
484
+ resetTelemetryStats() {
485
+ this.telemetry.resetStats();
486
+ }
487
+ };
488
+
489
+ // src/utils/validation.ts
490
+ function validateRequired(value, fieldName) {
491
+ if (value === void 0 || value === null || value === "") {
492
+ throw new ValidationError(`${fieldName} is required`);
493
+ }
494
+ }
495
+ function validateNonNegative(value, fieldName) {
496
+ if (typeof value !== "number" || isNaN(value)) {
497
+ throw new ValidationError(`${fieldName} must be a number`);
498
+ }
499
+ if (value < 0) {
500
+ throw new ValidationError(`${fieldName} must be non-negative`);
501
+ }
502
+ }
503
+ function validateDateString(value, fieldName) {
504
+ const date = value instanceof Date ? value : new Date(value);
505
+ if (isNaN(date.getTime())) {
506
+ throw new ValidationError(`${fieldName} must be a valid ISO date string`);
507
+ }
508
+ }
509
+ function validateUsageRecord(record) {
510
+ validateRequired(record.customerExternalId, "customerExternalId");
511
+ validateRequired(record.agentId, "agentId");
512
+ if (!record.signalName) {
513
+ throw new ValidationError("signalName is required");
514
+ }
515
+ if (record.quantity !== void 0 && record.quantity !== null) {
516
+ validateNonNegative(record.quantity, "quantity");
517
+ }
518
+ if (record.usageDate !== void 0) {
519
+ validateDateString(record.usageDate, "usageDate");
520
+ }
521
+ }
522
+ function validateUsageRecords(records) {
523
+ if (!Array.isArray(records)) {
524
+ throw new ValidationError("records must be an array");
525
+ }
526
+ if (records.length === 0) {
527
+ throw new ValidationError("records array cannot be empty");
528
+ }
529
+ records.forEach((record, index) => {
530
+ try {
531
+ validateUsageRecord(record);
532
+ } catch (error) {
533
+ if (error instanceof ValidationError) {
534
+ throw new ValidationError(`Invalid record at index ${index}: ${error.message}`);
535
+ }
536
+ throw error;
537
+ }
538
+ });
539
+ }
540
+ var KEY_PREFIXES = {
541
+ secret: "mf_sk_",
542
+ publishable: "mf_pk_"
543
+ };
544
+ function parseApiKey(apiKey) {
545
+ if (!apiKey || typeof apiKey !== "string") {
546
+ return null;
547
+ }
548
+ if (apiKey.startsWith(KEY_PREFIXES.secret)) {
549
+ return { type: "secret" };
550
+ }
551
+ if (apiKey.startsWith(KEY_PREFIXES.publishable)) {
552
+ return { type: "publishable" };
553
+ }
554
+ return null;
555
+ }
556
+ function validateApiKey(apiKey) {
557
+ if (!apiKey || typeof apiKey !== "string") {
558
+ throw new ValidationError("API key is required");
559
+ }
560
+ }
561
+
562
+ // src/resources/usage.ts
563
+ var UsageResource = class {
564
+ constructor(http) {
565
+ this.http = http;
566
+ }
567
+ /**
568
+ * Track events for a customer - supports both single record and batch operations
569
+ *
570
+ * @param params - Event tracking parameters (single record or batch)
571
+ * @returns Tracked event data
572
+ *
573
+ * @example
574
+ * ```typescript
575
+ * // Single event
576
+ * await client.usage.trackEvent({
577
+ * agentId: 'agent_123',
578
+ * customerExternalId: 'customer_456',
579
+ * signalName: 'api_call',
580
+ * quantity: 1
581
+ * });
582
+ *
583
+ * // Batch tracking
584
+ * await client.usage.trackEvent({
585
+ * records: [
586
+ * { customerExternalId: 'cust_1', agentId: 'agent_123', signalName: 'api_call', quantity: 10 },
587
+ * { customerExternalId: 'cust_2', agentId: 'agent_123', signalName: 'storage', quantity: 100 }
588
+ * ]
589
+ * });
590
+ * ```
591
+ */
592
+ async trackEvent(params) {
593
+ if ("records" in params) {
594
+ const records = params.records.map((record2) => this.normalizeRecord(record2));
595
+ validateUsageRecords(records);
596
+ const response2 = await this.http.post("/sdk/usage/record", {
597
+ records
598
+ });
599
+ return this.convertToBatchResponse(response2);
600
+ }
601
+ const record = this.normalizeRecord(params);
602
+ validateUsageRecord(record);
603
+ const response = await this.http.post("/sdk/usage/record", {
604
+ records: [record]
605
+ });
606
+ if (response.results.success.length === 1) {
607
+ const successResult = response.results.success[0];
608
+ return {
609
+ id: successResult.eventId,
610
+ success: true,
611
+ eventId: successResult.eventId,
612
+ rawEventId: successResult.rawEventId,
613
+ timestamp: successResult.timestamp
614
+ };
615
+ }
616
+ return this.convertToBatchResponse(response);
617
+ }
618
+ /**
619
+ * Record a single usage event
620
+ *
621
+ * @param record - The usage record to track
622
+ * @returns The response containing processing results
623
+ *
624
+ * @example
625
+ * ```typescript
626
+ * const result = await client.usage.record({
627
+ * customerExternalId: 'cust_123',
628
+ * agentId: 'agent_abc',
629
+ * signalName: 'api_call',
630
+ * quantity: 1,
631
+ * metadata: { model: 'gpt-4' }
632
+ * });
633
+ * ```
634
+ */
635
+ async record(record) {
636
+ const normalizedRecord = this.normalizeRecord(record);
637
+ validateUsageRecord(normalizedRecord);
638
+ return this.http.post("/sdk/usage/record", {
639
+ records: [normalizedRecord]
640
+ });
641
+ }
642
+ /**
643
+ * Record multiple usage events in a batch
644
+ *
645
+ * @param records - Array of usage records to track
646
+ * @returns The response containing processing results for all records
647
+ *
648
+ * @example
649
+ * ```typescript
650
+ * const result = await client.usage.recordBatch([
651
+ * { customerExternalId: 'cust_123', agentId: 'agent_abc', signalName: 'api_call', quantity: 5 },
652
+ * { customerExternalId: 'cust_456', agentId: 'agent_abc', signalName: 'api_call', quantity: 3 },
653
+ * ]);
654
+ * console.log(`Processed ${result.successful} of ${result.processed} records`);
655
+ * ```
656
+ */
657
+ async recordBatch(records) {
658
+ const normalizedRecords = records.map((record) => this.normalizeRecord(record));
659
+ validateUsageRecords(normalizedRecords);
660
+ return this.http.post("/sdk/usage/record", {
661
+ records: normalizedRecords
662
+ });
663
+ }
664
+ /**
665
+ * Normalize a usage record
666
+ */
667
+ normalizeRecord(record) {
668
+ const normalized = { ...record };
669
+ if (normalized.quantity === void 0) {
670
+ normalized.quantity = 1;
671
+ }
672
+ if (normalized.usageDate instanceof Date) {
673
+ normalized.usageDate = normalized.usageDate.toISOString();
674
+ }
675
+ return normalized;
676
+ }
677
+ /**
678
+ * Convert native response to BatchEventResponse format
679
+ */
680
+ convertToBatchResponse(response) {
681
+ return {
682
+ success: response.failed === 0,
683
+ totalRecords: response.processed,
684
+ successCount: response.successful,
685
+ failureCount: response.failed,
686
+ results: [
687
+ ...response.results.success.map((result) => ({
688
+ success: true,
689
+ responseData: {
690
+ id: result.eventId,
691
+ success: true,
692
+ eventId: result.eventId,
693
+ rawEventId: result.rawEventId,
694
+ timestamp: result.timestamp
695
+ }
696
+ })),
697
+ ...response.results.failed.map((result) => ({
698
+ success: false,
699
+ error: result.error,
700
+ originalData: result.record
701
+ }))
702
+ ]
703
+ };
704
+ }
705
+ };
706
+
707
+ // src/resources/customers.ts
708
+ var CustomersResource = class {
709
+ constructor(http) {
710
+ this.http = http;
711
+ }
712
+ /**
713
+ * Create a new customer
714
+ *
715
+ * @param params - Customer creation parameters
716
+ * @returns The created customer
717
+ *
718
+ * @example
719
+ * ```typescript
720
+ * const customer = await client.customers.create({
721
+ * name: 'Acme Corp',
722
+ * externalId: 'acme_123',
723
+ * email: 'billing@acme.com',
724
+ * agentId: 'agent_abc' // auto-creates subscription
725
+ * });
726
+ * ```
727
+ */
728
+ async create(params) {
729
+ validateRequired(params.name, "name");
730
+ return this.http.post("/sdk/customers", params);
731
+ }
732
+ /**
733
+ * Get a single customer by ID
734
+ *
735
+ * @param id - The customer's ID
736
+ * @returns The customer with subscriptions
737
+ *
738
+ * @example
739
+ * ```typescript
740
+ * const customer = await client.customers.get('cust_123');
741
+ * console.log(customer.name);
742
+ * ```
743
+ */
744
+ async get(id) {
745
+ validateRequired(id, "id");
746
+ return this.http.get(`/sdk/customers/${id}`);
747
+ }
748
+ /**
749
+ * Update an existing customer
750
+ *
751
+ * @param id - The customer's ID
752
+ * @param params - Fields to update
753
+ * @returns The updated customer
754
+ *
755
+ * @example
756
+ * ```typescript
757
+ * const customer = await client.customers.update('cust_123', {
758
+ * name: 'New Company Name',
759
+ * email: 'new-email@company.com'
760
+ * });
761
+ * ```
762
+ */
763
+ async update(id, params) {
764
+ validateRequired(id, "id");
765
+ return this.http.patch(`/sdk/customers/${id}`, params);
766
+ }
767
+ /**
768
+ * Delete a customer
769
+ *
770
+ * @param id - The customer's ID
771
+ *
772
+ * @example
773
+ * ```typescript
774
+ * await client.customers.delete('cust_123');
775
+ * ```
776
+ */
777
+ async delete(id) {
778
+ validateRequired(id, "id");
779
+ await this.http.delete(`/sdk/customers/${id}`);
780
+ }
781
+ /**
782
+ * List customers with pagination and filtering
783
+ *
784
+ * @param params - List parameters
785
+ * @returns Paginated list of customers
786
+ *
787
+ * @example
788
+ * ```typescript
789
+ * const { data, totalResults, hasMore } = await client.customers.list({
790
+ * limit: 10,
791
+ * page: 1
792
+ * });
793
+ * ```
794
+ */
795
+ async list(params) {
796
+ const response = await this.http.get("/sdk/customers", params);
797
+ if (Array.isArray(response)) {
798
+ return {
799
+ data: response,
800
+ totalResults: response.length,
801
+ page: params?.page || 1,
802
+ limit: params?.limit || response.length,
803
+ hasMore: false
804
+ };
805
+ }
806
+ return response;
807
+ }
808
+ };
809
+
810
+ // src/resources/invoices.ts
811
+ var InvoicesResource = class {
812
+ constructor(http) {
813
+ this.http = http;
814
+ }
815
+ /**
816
+ * List invoices with optional filters
817
+ *
818
+ * @param params - Optional filter parameters
819
+ * @returns Paginated list of invoices
820
+ *
821
+ * @example
822
+ * ```typescript
823
+ * const { invoices, totalResults } = await client.invoices.list({
824
+ * customerId: 'cust_123',
825
+ * status: 'pending',
826
+ * page: 1,
827
+ * limit: 20
828
+ * });
829
+ * ```
830
+ */
831
+ async list(params) {
832
+ return this.http.get("/sdk/invoices", params);
833
+ }
834
+ /**
835
+ * Get a single invoice by ID
836
+ *
837
+ * @param invoiceId - The invoice's ID
838
+ * @returns The invoice with full details including line items and payments
839
+ *
840
+ * @example
841
+ * ```typescript
842
+ * const invoice = await client.invoices.get('inv_abc');
843
+ * console.log(`Invoice ${invoice.invoiceNumber}: ${invoice.totalAmount}`);
844
+ * ```
845
+ */
846
+ async get(invoiceId) {
847
+ validateRequired(invoiceId, "invoiceId");
848
+ return this.http.get(`/sdk/invoices/${invoiceId}`);
849
+ }
850
+ };
851
+
852
+ // src/resources/analytics.ts
853
+ var AnalyticsResource = class {
854
+ constructor(http) {
855
+ this.http = http;
856
+ }
857
+ /**
858
+ * Get usage analytics for a date range
859
+ *
860
+ * @param params - Analytics query parameters
861
+ * @returns Usage analytics with summary and time-series data
862
+ *
863
+ * @example
864
+ * ```typescript
865
+ * const analytics = await client.analytics.usage({
866
+ * startDate: '2024-01-01',
867
+ * endDate: '2024-01-31',
868
+ * groupBy: 'daily',
869
+ * customerId: 'cust_123'
870
+ * });
871
+ *
872
+ * console.log(`Total usage: ${analytics.summary.totalQuantity}`);
873
+ * console.log(`Total cost: $${analytics.summary.totalCost}`);
874
+ *
875
+ * // Time series data
876
+ * analytics.data.forEach(point => {
877
+ * console.log(`${point.date}: ${point.quantity} units, $${point.cost}`);
878
+ * });
879
+ * ```
880
+ */
881
+ async usage(params) {
882
+ validateRequired(params.startDate, "startDate");
883
+ validateRequired(params.endDate, "endDate");
884
+ validateDateString(params.startDate, "startDate");
885
+ validateDateString(params.endDate, "endDate");
886
+ return this.http.get("/sdk/analytics/usage", params);
887
+ }
888
+ };
889
+
890
+ // src/resources/subscriptions.ts
891
+ var SubscriptionsResource = class {
892
+ constructor(http) {
893
+ this.http = http;
894
+ }
895
+ /**
896
+ * List subscriptions with optional filters
897
+ *
898
+ * @param params - Optional filter parameters
899
+ * @returns Paginated list of subscriptions
900
+ *
901
+ * @example
902
+ * ```typescript
903
+ * const { subscriptions, totalResults } = await client.subscriptions.list({
904
+ * status: 'active',
905
+ * customerId: 'cust_123',
906
+ * page: 1,
907
+ * limit: 20
908
+ * });
909
+ * ```
910
+ */
911
+ async list(params) {
912
+ return this.http.get("/sdk/subscriptions", params);
913
+ }
914
+ /**
915
+ * Get a single subscription by ID
916
+ *
917
+ * @param subscriptionId - The subscription's ID
918
+ * @returns The subscription with full details including usage summary
919
+ *
920
+ * @example
921
+ * ```typescript
922
+ * const sub = await client.subscriptions.get('sub_abc');
923
+ * console.log(`Status: ${sub.status}`);
924
+ * console.log(`Usage this period: ${sub.usage.totalQuantity}`);
925
+ * ```
926
+ */
927
+ async get(subscriptionId) {
928
+ validateRequired(subscriptionId, "subscriptionId");
929
+ return this.http.get(`/sdk/subscriptions/${subscriptionId}`);
930
+ }
931
+ };
932
+
933
+ // src/resources/portal-sessions.ts
934
+ var PortalSessionsResource = class {
935
+ constructor(http, assertSecretKey) {
936
+ this.http = http;
937
+ this.assertSecretKey = assertSecretKey;
938
+ }
939
+ /**
940
+ * Create a new portal session
941
+ *
942
+ * Creates a short-lived portal session for a customer.
943
+ * The returned URL can be used to redirect the customer to their billing portal.
944
+ *
945
+ * @param params - Portal session parameters
946
+ * @returns Created portal session with URL and token
947
+ * @throws ValidationError if neither customerId nor customerExternalId is provided
948
+ * @throws AuthenticationError if using a publishable key
949
+ *
950
+ * @example
951
+ * ```typescript
952
+ * // By customer ID
953
+ * const session = await client.portalSessions.create({
954
+ * customerId: 'cust_123',
955
+ * returnUrl: 'https://myapp.com/account'
956
+ * });
957
+ *
958
+ * // By external ID
959
+ * const session = await client.portalSessions.create({
960
+ * customerExternalId: 'your_customer_id',
961
+ * features: ['invoices', 'usage']
962
+ * });
963
+ *
964
+ * console.log(session.url); // Redirect customer here
965
+ * ```
966
+ */
967
+ async create(params) {
968
+ this.assertSecretKey();
969
+ if (!params.customerId && !params.customerExternalId) {
970
+ throw new ValidationError(
971
+ "Either customerId or customerExternalId is required"
972
+ );
973
+ }
974
+ return this.http.post("/sdk/portal-sessions", params);
975
+ }
976
+ /**
977
+ * Get a portal session by ID
978
+ *
979
+ * @param sessionId - Portal session ID
980
+ * @returns Portal session details
981
+ *
982
+ * @example
983
+ * ```typescript
984
+ * const session = await client.portalSessions.get('ps_123');
985
+ * console.log(session.expiresAt);
986
+ * ```
987
+ */
988
+ async get(sessionId) {
989
+ this.assertSecretKey();
990
+ if (!sessionId) {
991
+ throw new ValidationError("sessionId is required");
992
+ }
993
+ return this.http.get(`/sdk/portal-sessions/${sessionId}`);
994
+ }
995
+ /**
996
+ * List portal sessions
997
+ *
998
+ * @param params - Optional filters
999
+ * @returns List of portal sessions
1000
+ *
1001
+ * @example
1002
+ * ```typescript
1003
+ * // List all active sessions
1004
+ * const { data } = await client.portalSessions.list();
1005
+ *
1006
+ * // List sessions for a specific customer
1007
+ * const { data } = await client.portalSessions.list({
1008
+ * customerId: 'cust_123',
1009
+ * includeExpired: true
1010
+ * });
1011
+ * ```
1012
+ */
1013
+ async list(params) {
1014
+ this.assertSecretKey();
1015
+ return this.http.get("/sdk/portal-sessions", {
1016
+ customerId: params?.customerId,
1017
+ limit: params?.limit,
1018
+ includeExpired: params?.includeExpired
1019
+ });
1020
+ }
1021
+ /**
1022
+ * Revoke a portal session
1023
+ *
1024
+ * Immediately invalidates the session, preventing further access.
1025
+ *
1026
+ * @param sessionId - Portal session ID to revoke
1027
+ *
1028
+ * @example
1029
+ * ```typescript
1030
+ * await client.portalSessions.revoke('ps_123');
1031
+ * ```
1032
+ */
1033
+ async revoke(sessionId) {
1034
+ this.assertSecretKey();
1035
+ if (!sessionId) {
1036
+ throw new ValidationError("sessionId is required");
1037
+ }
1038
+ await this.http.delete(`/sdk/portal-sessions/${sessionId}`);
1039
+ }
1040
+ };
1041
+
1042
+ // src/client.ts
1043
+ var MarginFrontClient = class {
1044
+ /**
1045
+ * Create a new MarginFront client
1046
+ *
1047
+ * @param apiKeyOrConfig - Either an API key string or a configuration object
1048
+ * @param options - Optional client options (when first param is API key)
1049
+ *
1050
+ * @example
1051
+ * ```typescript
1052
+ * // Simple initialization
1053
+ * const client = new MarginFrontClient('mf_sk_your_secret_key');
1054
+ *
1055
+ * // With options
1056
+ * const client = new MarginFrontClient('mf_sk_your_secret_key', {
1057
+ * timeout: 10000,
1058
+ * retries: 3
1059
+ * });
1060
+ *
1061
+ * // With config object
1062
+ * const client = new MarginFrontClient({
1063
+ * apiKey: 'mf_sk_your_secret_key',
1064
+ * baseUrl: 'https://api.example.com/v1',
1065
+ * debug: true
1066
+ * });
1067
+ * ```
1068
+ */
1069
+ constructor(apiKeyOrConfig, options = {}) {
1070
+ this._orgInfo = null;
1071
+ let config;
1072
+ if (typeof apiKeyOrConfig === "string") {
1073
+ config = {
1074
+ apiKey: apiKeyOrConfig,
1075
+ ...options
1076
+ };
1077
+ } else {
1078
+ config = apiKeyOrConfig;
1079
+ }
1080
+ validateApiKey(config.apiKey);
1081
+ this._apiKey = config.apiKey;
1082
+ const keyInfo = parseApiKey(config.apiKey);
1083
+ this._keyType = keyInfo?.type ?? "secret";
1084
+ this.http = new HttpClient(config);
1085
+ this.usage = new UsageResource(this.http);
1086
+ this.customers = new CustomersResource(this.http);
1087
+ this.invoices = new InvoicesResource(this.http);
1088
+ this.analytics = new AnalyticsResource(this.http);
1089
+ this.subscriptions = new SubscriptionsResource(this.http);
1090
+ this.portalSessions = new PortalSessionsResource(
1091
+ this.http,
1092
+ () => this.assertSecretKey()
1093
+ );
1094
+ }
1095
+ /**
1096
+ * No-op — the server enforces key type for secret-only operations.
1097
+ * Kept so PortalSessionsResource can call it without breaking.
1098
+ */
1099
+ assertSecretKey() {
1100
+ }
1101
+ /**
1102
+ * Connect to the MarginFront API and verify credentials
1103
+ *
1104
+ * This must be called once before using any API methods that require verification.
1105
+ * Optional but recommended for validating your API key.
1106
+ *
1107
+ * @returns Promise resolving to the client instance for chaining
1108
+ * @throws AuthenticationError if API key is invalid
1109
+ * @throws InitializationError for other initialization errors
1110
+ *
1111
+ * @example
1112
+ * ```typescript
1113
+ * const client = new MarginFrontClient('mf_sk_your_secret_key');
1114
+ *
1115
+ * try {
1116
+ * await client.connect();
1117
+ * console.log('Connected to MarginFront API');
1118
+ * } catch (error) {
1119
+ * console.error('Failed to connect:', error);
1120
+ * }
1121
+ * ```
1122
+ */
1123
+ async connect() {
1124
+ try {
1125
+ const result = await this.http.get("/sdk/verify");
1126
+ if (!result || !result.organization || !result.organization.id) {
1127
+ throw new InitializationError("Unexpected response format from API key verification");
1128
+ }
1129
+ this._orgInfo = result.organization;
1130
+ return this;
1131
+ } catch (error) {
1132
+ const err = error;
1133
+ const statusCode = err.statusCode || err.response?.status;
1134
+ const errorMessage = err.message || "Unknown error";
1135
+ const requestId = err.requestId || "unknown";
1136
+ if (statusCode === 401) {
1137
+ throw new AuthenticationError(
1138
+ "Invalid API key. Please check your API key and try again.",
1139
+ { requestId }
1140
+ );
1141
+ }
1142
+ throw new InitializationError(`Connection failed: ${errorMessage}`, { requestId });
1143
+ }
1144
+ }
1145
+ /**
1146
+ * Verify the API key and get organization details
1147
+ *
1148
+ * @returns Verification result with organization info
1149
+ *
1150
+ * @example
1151
+ * ```typescript
1152
+ * const result = await client.verify();
1153
+ * if (result.verified) {
1154
+ * console.log(`Connected to ${result.organization.name}`);
1155
+ * }
1156
+ * ```
1157
+ */
1158
+ async verify() {
1159
+ const result = await this.http.get("/sdk/verify");
1160
+ if (result.organization && !this._orgInfo) {
1161
+ this._orgInfo = result.organization;
1162
+ }
1163
+ return result;
1164
+ }
1165
+ /**
1166
+ * Get organization information
1167
+ *
1168
+ * @returns Organization information from API key verification
1169
+ * @throws Error if organization info is not available (call connect() first)
1170
+ *
1171
+ * @example
1172
+ * ```typescript
1173
+ * await client.connect();
1174
+ * const org = client.getOrganization();
1175
+ * console.log(`Organization: ${org.name}`);
1176
+ * ```
1177
+ */
1178
+ getOrganization() {
1179
+ if (!this._orgInfo) {
1180
+ throw new Error(
1181
+ "Organization information is not available. Make sure the API key is valid and you have called connect()"
1182
+ );
1183
+ }
1184
+ return { ...this._orgInfo };
1185
+ }
1186
+ /**
1187
+ * Track event for a customer (shorthand method)
1188
+ *
1189
+ * @param params - Event tracking parameters
1190
+ * @returns Tracked event data
1191
+ *
1192
+ * @example
1193
+ * ```typescript
1194
+ * // Simple event tracking
1195
+ * await client.trackEvent({
1196
+ * agentId: 'agent_123',
1197
+ * customerExternalId: 'customer_456',
1198
+ * signalName: 'api_call',
1199
+ * quantity: 1
1200
+ * });
1201
+ *
1202
+ * // Batch tracking
1203
+ * await client.trackEvent({
1204
+ * records: [
1205
+ * { customerExternalId: 'cust_1', agentId: 'agent_123', signalName: 'api_call', quantity: 10 },
1206
+ * { customerExternalId: 'cust_2', agentId: 'agent_123', signalName: 'storage', quantity: 100 }
1207
+ * ]
1208
+ * });
1209
+ * ```
1210
+ */
1211
+ async trackEvent(params) {
1212
+ return this.usage.trackEvent(params);
1213
+ }
1214
+ /**
1215
+ * Re-verify the API key
1216
+ *
1217
+ * Useful if you suspect the API key status might have changed
1218
+ *
1219
+ * @returns Updated organization information
1220
+ */
1221
+ async verifyApiKey() {
1222
+ const result = await this.verify();
1223
+ if (!result.organization) {
1224
+ throw new InitializationError("Unexpected response format from API key verification");
1225
+ }
1226
+ this._orgInfo = result.organization;
1227
+ return { ...this._orgInfo };
1228
+ }
1229
+ /**
1230
+ * Get current telemetry statistics
1231
+ *
1232
+ * @returns Telemetry statistics
1233
+ *
1234
+ * @example
1235
+ * ```typescript
1236
+ * const stats = client.getTelemetryStats();
1237
+ * console.log(`Total Requests: ${stats.requestCount}`);
1238
+ * console.log(`Success Rate: ${(stats.successRate * 100).toFixed(2)}%`);
1239
+ * ```
1240
+ */
1241
+ getTelemetryStats() {
1242
+ return this.http.getTelemetryStats();
1243
+ }
1244
+ /**
1245
+ * Reset telemetry statistics
1246
+ */
1247
+ resetTelemetryStats() {
1248
+ this.http.resetTelemetryStats();
1249
+ }
1250
+ /**
1251
+ * Get the API key type (secret or publishable)
1252
+ *
1253
+ * @returns 'secret' for mf_sk_* keys, 'publishable' for mf_pk_* keys
1254
+ *
1255
+ * @example
1256
+ * ```typescript
1257
+ * if (client.getKeyType() === 'secret') {
1258
+ * const session = await client.portalSessions.create({ ... });
1259
+ * }
1260
+ * ```
1261
+ */
1262
+ getKeyType() {
1263
+ return this._keyType;
1264
+ }
1265
+ /**
1266
+ * Check if the API key is a secret key (mf_sk_*)
1267
+ *
1268
+ * @returns true if the key can perform server-side operations
1269
+ */
1270
+ isSecretKey() {
1271
+ return this._keyType === "secret";
1272
+ }
1273
+ /**
1274
+ * Check if the API key is a publishable key (mf_pk_*)
1275
+ *
1276
+ * @returns true if the key is safe for frontend use
1277
+ */
1278
+ isPublishableKey() {
1279
+ return this._keyType === "publishable";
1280
+ }
1281
+ /**
1282
+ * Get a masked version of the API key for debugging
1283
+ *
1284
+ * @returns Masked API key showing only prefix and last 4 characters
1285
+ */
1286
+ getMaskedApiKey() {
1287
+ if (this._apiKey.length <= 20) {
1288
+ return this._apiKey.substring(0, 8) + "...";
1289
+ }
1290
+ return this._apiKey.substring(0, 16) + "..." + this._apiKey.slice(-4);
1291
+ }
1292
+ };
1293
+
1294
+ // src/cli/utils/env.ts
1295
+ var import_fs = require("fs");
1296
+ var import_path = require("path");
1297
+ var ENV_FILE = ".env.marginfront.cli";
1298
+ function loadCliEnv() {
1299
+ const filePath = (0, import_path.join)(process.cwd(), ENV_FILE);
1300
+ try {
1301
+ const contents = (0, import_fs.readFileSync)(filePath, "utf-8");
1302
+ const env = {};
1303
+ for (const line of contents.split("\n")) {
1304
+ const trimmed = line.trim();
1305
+ if (!trimmed || trimmed.startsWith("#")) continue;
1306
+ const eqIdx = trimmed.indexOf("=");
1307
+ if (eqIdx === -1) continue;
1308
+ const key = trimmed.slice(0, eqIdx).trim();
1309
+ const value = trimmed.slice(eqIdx + 1).trim();
1310
+ env[key] = value;
1311
+ }
1312
+ return env;
1313
+ } catch {
1314
+ return {};
1315
+ }
1316
+ }
1317
+
1318
+ // src/cli/commands/verify.ts
1319
+ function verifyCommand() {
1320
+ return new import_commander.Command("verify").description("Verify an API key and display organization details").option("--api-key <key>", "API key (mf_sk_* or mf_pk_*)").option("--base-url <url>", "API base URL (default: https://api.revmax.ai/v1)").option("--debug", "Enable debug output").action(async (options) => {
1321
+ const env = loadCliEnv();
1322
+ const apiKey = options.apiKey ?? env["MF_API_KEY"];
1323
+ const baseUrl = options.baseUrl ?? env["MF_BASE_URL"];
1324
+ const debug = options.debug ?? env["MF_DEBUG"] === "true";
1325
+ if (!apiKey) {
1326
+ console.error("Error: --api-key is required (or set MF_API_KEY in .env.marginfront.cli)");
1327
+ process.exit(1);
1328
+ }
1329
+ if (debug) {
1330
+ console.log("[debug] base-url:", baseUrl ?? "(SDK default)");
1331
+ console.log("[debug] api-key:", apiKey.slice(0, 10) + "...");
1332
+ }
1333
+ try {
1334
+ const client = new MarginFrontClient({ apiKey, ...baseUrl ? { baseUrl } : {} });
1335
+ const result = await client.verify();
1336
+ console.log("\nVerified successfully\n");
1337
+ console.log(JSON.stringify(result, null, 2));
1338
+ } catch (err) {
1339
+ const message = err instanceof Error ? err.message : String(err);
1340
+ console.error("\nVerification failed:", message);
1341
+ process.exit(1);
1342
+ }
1343
+ });
1344
+ }
1345
+
1346
+ // src/cli/commands/track-event.ts
1347
+ var import_commander2 = require("commander");
1348
+ function trackEventCommand() {
1349
+ return new import_commander2.Command("track-event").description("Send a usage event to the MarginFront API").option("--api-key <key>", "API key (mf_sk_*)").option("--base-url <url>", "API base URL (default: https://api.revmax.ai/v1)").option("--customer-id <id>", "Customer external ID").option("--agent-id <id>", "Agent UUID").option("--signal <name>", "Signal name (e.g. CALL_MINUTES)").option("--quantity <number>", "Quantity to record (default: 1)", "1").option("--metadata <json>", "Optional metadata as a JSON string").option("--debug", "Enable debug output").action(async (options) => {
1350
+ const env = loadCliEnv();
1351
+ const apiKey = options.apiKey ?? env["MF_API_KEY"];
1352
+ const baseUrl = options.baseUrl ?? env["MF_BASE_URL"];
1353
+ const customerId = options.customerId ?? env["MF_CUSTOMER_ID"];
1354
+ const agentId = options.agentId ?? env["MF_AGENT_ID"];
1355
+ const signal = options.signal ?? env["MF_SIGNAL"];
1356
+ const quantity = Number(options.quantity ?? env["MF_QUANTITY"] ?? "1");
1357
+ const debug = options.debug ?? env["MF_DEBUG"] === "true";
1358
+ const missing = [];
1359
+ if (!apiKey) missing.push("--api-key (or MF_API_KEY)");
1360
+ if (!customerId) missing.push("--customer-id (or MF_CUSTOMER_ID)");
1361
+ if (!agentId) missing.push("--agent-id (or MF_AGENT_ID)");
1362
+ if (!signal) missing.push("--signal (or MF_SIGNAL)");
1363
+ if (missing.length > 0) {
1364
+ console.error("Error: missing required options:\n " + missing.join("\n "));
1365
+ process.exit(1);
1366
+ }
1367
+ if (debug) {
1368
+ console.log("[debug] base-url:", baseUrl ?? "(SDK default)");
1369
+ console.log("[debug] api-key:", apiKey.slice(0, 10) + "...");
1370
+ console.log("[debug] payload:", { customerId, agentId, signal, quantity });
1371
+ }
1372
+ let metadata;
1373
+ if (options.metadata) {
1374
+ try {
1375
+ metadata = JSON.parse(options.metadata);
1376
+ } catch {
1377
+ console.error("Error: --metadata must be valid JSON");
1378
+ process.exit(1);
1379
+ }
1380
+ }
1381
+ try {
1382
+ const client = new MarginFrontClient({ apiKey, ...baseUrl ? { baseUrl } : {} });
1383
+ const result = await client.trackEvent({
1384
+ customerExternalId: customerId,
1385
+ agentId,
1386
+ signalName: signal,
1387
+ quantity,
1388
+ ...metadata ? { metadata } : {}
1389
+ });
1390
+ console.log("\nEvent tracked successfully\n");
1391
+ console.log(JSON.stringify(result, null, 2));
1392
+ } catch (err) {
1393
+ const message = err instanceof Error ? err.message : String(err);
1394
+ console.error("\nFailed to track event:", message);
1395
+ process.exit(1);
1396
+ }
1397
+ });
1398
+ }
1399
+
1400
+ // src/cli/index.ts
1401
+ var WARNING = `
1402
+ \x1B[33m\u26A0\uFE0F CLI mode \u2014 for testing only.\x1B[0m
1403
+ \x1B[33m .env.marginfront.cli is NOT used when the SDK is imported as a library.\x1B[0m
1404
+ `;
1405
+ var program = new import_commander3.Command().name("mf").description("MarginFront SDK CLI (testing tool)").version("0.0.1").hook("preAction", () => {
1406
+ console.log(WARNING);
1407
+ });
1408
+ program.addCommand(verifyCommand());
1409
+ program.addCommand(trackEventCommand());
1410
+ program.parse();