@marginfront/sdk 0.0.1 → 0.1.2

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,194 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ MarginFrontClient
4
+ } from "../chunk-UIM2U5MX.mjs";
5
+
6
+ // src/cli/index.ts
7
+ import { Command as Command3 } from "commander";
8
+
9
+ // src/cli/commands/verify.ts
10
+ import { Command } from "commander";
11
+
12
+ // src/cli/utils/env.ts
13
+ import { readFileSync } from "fs";
14
+ import { join } from "path";
15
+ var ENV_FILE = ".env.marginfront.cli";
16
+ function loadCliEnv() {
17
+ const filePath = join(process.cwd(), ENV_FILE);
18
+ try {
19
+ const contents = readFileSync(filePath, "utf-8");
20
+ const env = {};
21
+ for (const line of contents.split("\n")) {
22
+ const trimmed = line.trim();
23
+ if (!trimmed || trimmed.startsWith("#")) continue;
24
+ const eqIdx = trimmed.indexOf("=");
25
+ if (eqIdx === -1) continue;
26
+ const key = trimmed.slice(0, eqIdx).trim();
27
+ const value = trimmed.slice(eqIdx + 1).trim();
28
+ env[key] = value;
29
+ }
30
+ return env;
31
+ } catch {
32
+ return {};
33
+ }
34
+ }
35
+
36
+ // src/cli/commands/verify.ts
37
+ function verifyCommand() {
38
+ return new 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) => {
39
+ const env = loadCliEnv();
40
+ const apiKey = options.apiKey ?? env["MF_API_KEY"];
41
+ const baseUrl = options.baseUrl ?? env["MF_BASE_URL"];
42
+ const debug = options.debug ?? env["MF_DEBUG"] === "true";
43
+ if (!apiKey) {
44
+ console.error("Error: --api-key is required (or set MF_API_KEY in .env.marginfront.cli)");
45
+ process.exit(1);
46
+ }
47
+ if (debug) {
48
+ console.log("[debug] base-url:", baseUrl ?? "(SDK default)");
49
+ console.log("[debug] api-key:", apiKey.slice(0, 10) + "...");
50
+ }
51
+ try {
52
+ const client = new MarginFrontClient({ apiKey, ...baseUrl ? { baseUrl } : {} });
53
+ const result = await client.verify();
54
+ console.log("\nVerified successfully\n");
55
+ console.log(JSON.stringify(result, null, 2));
56
+ } catch (err) {
57
+ const message = err instanceof Error ? err.message : String(err);
58
+ console.error("\nVerification failed:", message);
59
+ process.exit(1);
60
+ }
61
+ });
62
+ }
63
+
64
+ // src/cli/commands/track-event.ts
65
+ import { Command as Command2 } from "commander";
66
+ function trackEventCommand() {
67
+ return new Command2("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) => {
68
+ const env = loadCliEnv();
69
+ const apiKey = options.apiKey ?? env["MF_API_KEY"];
70
+ const baseUrl = options.baseUrl ?? env["MF_BASE_URL"];
71
+ const customerId = options.customerId ?? env["MF_CUSTOMER_ID"];
72
+ const agentId = options.agentId ?? env["MF_AGENT_ID"];
73
+ const signal = options.signal ?? env["MF_SIGNAL"];
74
+ const quantity = Number(options.quantity ?? env["MF_QUANTITY"] ?? "1");
75
+ const debug = options.debug ?? env["MF_DEBUG"] === "true";
76
+ const missing = [];
77
+ if (!apiKey) missing.push("--api-key (or MF_API_KEY)");
78
+ if (!customerId) missing.push("--customer-id (or MF_CUSTOMER_ID)");
79
+ if (!agentId) missing.push("--agent-id (or MF_AGENT_ID)");
80
+ if (!signal) missing.push("--signal (or MF_SIGNAL)");
81
+ if (missing.length > 0) {
82
+ console.error("Error: missing required options:\n " + missing.join("\n "));
83
+ process.exit(1);
84
+ }
85
+ if (debug) {
86
+ console.log("[debug] base-url:", baseUrl ?? "(SDK default)");
87
+ console.log("[debug] api-key:", apiKey.slice(0, 10) + "...");
88
+ console.log("[debug] payload:", { customerId, agentId, signal, quantity });
89
+ }
90
+ let metadata;
91
+ if (options.metadata) {
92
+ try {
93
+ metadata = JSON.parse(options.metadata);
94
+ } catch {
95
+ console.error("Error: --metadata must be valid JSON");
96
+ process.exit(1);
97
+ }
98
+ }
99
+ try {
100
+ const client = new MarginFrontClient({ apiKey, ...baseUrl ? { baseUrl } : {} });
101
+ const result = await client.trackEvent({
102
+ customerExternalId: customerId,
103
+ agentId,
104
+ signalName: signal,
105
+ quantity,
106
+ ...metadata ? { metadata } : {}
107
+ });
108
+ console.log("\nEvent tracked successfully\n");
109
+ console.log(JSON.stringify(result, null, 2));
110
+ } catch (err) {
111
+ const message = err instanceof Error ? err.message : String(err);
112
+ console.error("\nFailed to track event:", message);
113
+ process.exit(1);
114
+ }
115
+ });
116
+ }
117
+
118
+ // package.json
119
+ var package_default = {
120
+ name: "@marginfront/sdk",
121
+ version: "0.1.2",
122
+ description: "Official Node.js SDK for MarginFront - usage-based billing, invoicing, and analytics",
123
+ main: "dist/index.js",
124
+ module: "dist/index.mjs",
125
+ types: "dist/index.d.ts",
126
+ exports: {
127
+ ".": {
128
+ types: "./dist/index.d.ts",
129
+ import: "./dist/index.mjs",
130
+ require: "./dist/index.js"
131
+ }
132
+ },
133
+ bin: {
134
+ mf: "dist/cli/index.js"
135
+ },
136
+ files: [
137
+ "dist"
138
+ ],
139
+ scripts: {
140
+ build: "tsup src/index.ts src/cli/index.ts --format cjs,esm --dts --clean",
141
+ test: "vitest",
142
+ lint: "eslint src/",
143
+ prepublishOnly: "npm run build"
144
+ },
145
+ keywords: [
146
+ "marginfront",
147
+ "billing",
148
+ "usage-based",
149
+ "invoicing",
150
+ "sdk",
151
+ "api",
152
+ "metering",
153
+ "subscriptions"
154
+ ],
155
+ author: "MarginFront",
156
+ license: "MIT",
157
+ repository: {
158
+ type: "git",
159
+ url: "https://github.com/Lowcountry-AI/platform",
160
+ directory: "packages/sdk"
161
+ },
162
+ homepage: "https://marginfront.com",
163
+ bugs: {
164
+ url: "https://github.com/Lowcountry-AI/platform/issues"
165
+ },
166
+ engines: {
167
+ node: ">=16.0.0"
168
+ },
169
+ dependencies: {
170
+ axios: "^1.6.0",
171
+ commander: "^12.0.0"
172
+ },
173
+ devDependencies: {
174
+ "@types/node": "^20.0.0",
175
+ "@typescript-eslint/eslint-plugin": "^6.21.0",
176
+ "@typescript-eslint/parser": "^6.21.0",
177
+ eslint: "^8.57.1",
178
+ tsup: "^8.0.0",
179
+ typescript: "^5.3.0",
180
+ vitest: "^1.0.0"
181
+ }
182
+ };
183
+
184
+ // src/cli/index.ts
185
+ var WARNING = `
186
+ \x1B[33m\u26A0\uFE0F CLI mode \u2014 for testing only.\x1B[0m
187
+ \x1B[33m .env.marginfront.cli is NOT used when the SDK is imported as a library.\x1B[0m
188
+ `;
189
+ var program = new Command3().name("mf").description("MarginFront SDK CLI (testing tool)").version(package_default.version).hook("preAction", () => {
190
+ console.log(WARNING);
191
+ });
192
+ program.addCommand(verifyCommand());
193
+ program.addCommand(trackEventCommand());
194
+ program.parse();
package/dist/index.d.mts CHANGED
@@ -566,27 +566,23 @@ interface ParsedApiKey {
566
566
  type: ApiKeyType;
567
567
  }
568
568
  /**
569
- * Parse an API key to extract type
569
+ * Parse an API key prefix to infer a display hint for the key type.
570
570
  *
571
- * @param apiKey - The API key to parse
572
- * @returns Parsed key info or null if invalid format
571
+ * This is a convenience utility for UIs that want to show "secret" or
572
+ * "publishable" based on the key prefix. The server is the authoritative
573
+ * source of key validity and type — this function does NOT gate access.
573
574
  *
574
- * @example
575
- * ```typescript
576
- * const info = parseApiKey('mf_sk_xxx');
577
- * // { type: 'secret' }
578
- *
579
- * const pub = parseApiKey('mf_pk_xxx');
580
- * // { type: 'publishable' }
581
- * ```
575
+ * @returns ParsedApiKey with type hint, or null if prefix is unrecognized
582
576
  */
583
577
  declare function parseApiKey(apiKey: string): ParsedApiKey | null;
584
578
  /**
585
- * Check if an API key is a secret key (mf_sk_*)
579
+ * Check if an API key looks like a secret key based on its prefix.
580
+ * Cosmetic hint only — the server is the authoritative source.
586
581
  */
587
582
  declare function isSecretKey(apiKey: string): boolean;
588
583
  /**
589
- * Check if an API key is a publishable key (mf_pk_*)
584
+ * Check if an API key looks like a publishable key based on its prefix.
585
+ * Cosmetic hint only — the server is the authoritative source.
590
586
  */
591
587
  declare function isPublishableKey(apiKey: string): boolean;
592
588
 
@@ -1195,8 +1191,8 @@ declare class MarginFrontClient {
1195
1191
  */
1196
1192
  constructor(apiKeyOrConfig: string | MarginFrontConfig, options?: ClientOptions);
1197
1193
  /**
1198
- * Assert that the current API key is a secret key
1199
- * @throws ValidationError if using a publishable key
1194
+ * No-op the server enforces key type for secret-only operations.
1195
+ * Kept so PortalSessionsResource can call it without breaking.
1200
1196
  */
1201
1197
  private assertSecretKey;
1202
1198
  /**
package/dist/index.d.ts CHANGED
@@ -566,27 +566,23 @@ interface ParsedApiKey {
566
566
  type: ApiKeyType;
567
567
  }
568
568
  /**
569
- * Parse an API key to extract type
569
+ * Parse an API key prefix to infer a display hint for the key type.
570
570
  *
571
- * @param apiKey - The API key to parse
572
- * @returns Parsed key info or null if invalid format
571
+ * This is a convenience utility for UIs that want to show "secret" or
572
+ * "publishable" based on the key prefix. The server is the authoritative
573
+ * source of key validity and type — this function does NOT gate access.
573
574
  *
574
- * @example
575
- * ```typescript
576
- * const info = parseApiKey('mf_sk_xxx');
577
- * // { type: 'secret' }
578
- *
579
- * const pub = parseApiKey('mf_pk_xxx');
580
- * // { type: 'publishable' }
581
- * ```
575
+ * @returns ParsedApiKey with type hint, or null if prefix is unrecognized
582
576
  */
583
577
  declare function parseApiKey(apiKey: string): ParsedApiKey | null;
584
578
  /**
585
- * Check if an API key is a secret key (mf_sk_*)
579
+ * Check if an API key looks like a secret key based on its prefix.
580
+ * Cosmetic hint only — the server is the authoritative source.
586
581
  */
587
582
  declare function isSecretKey(apiKey: string): boolean;
588
583
  /**
589
- * Check if an API key is a publishable key (mf_pk_*)
584
+ * Check if an API key looks like a publishable key based on its prefix.
585
+ * Cosmetic hint only — the server is the authoritative source.
590
586
  */
591
587
  declare function isPublishableKey(apiKey: string): boolean;
592
588
 
@@ -1195,8 +1191,8 @@ declare class MarginFrontClient {
1195
1191
  */
1196
1192
  constructor(apiKeyOrConfig: string | MarginFrontConfig, options?: ClientOptions);
1197
1193
  /**
1198
- * Assert that the current API key is a secret key
1199
- * @throws ValidationError if using a publishable key
1194
+ * No-op the server enforces key type for secret-only operations.
1195
+ * Kept so PortalSessionsResource can call it without breaking.
1200
1196
  */
1201
1197
  private assertSecretKey;
1202
1198
  /**
package/dist/index.js CHANGED
@@ -583,24 +583,10 @@ function isPublishableKey(apiKey) {
583
583
  const parsed = parseApiKey(apiKey);
584
584
  return parsed !== null && parsed.type === "publishable";
585
585
  }
586
- function getMinKeyLength(prefix) {
587
- return prefix.length + 16;
588
- }
589
586
  function validateApiKey(apiKey) {
590
587
  if (!apiKey || typeof apiKey !== "string") {
591
588
  throw new ValidationError("API key is required");
592
589
  }
593
- const parsed = parseApiKey(apiKey);
594
- if (!parsed) {
595
- throw new ValidationError(
596
- "Invalid API key format. Expected mf_sk_* or mf_pk_*"
597
- );
598
- }
599
- const prefix = KEY_PREFIXES[parsed.type];
600
- const minLength = getMinKeyLength(prefix);
601
- if (apiKey.length < minLength) {
602
- throw new ValidationError("Invalid API key format. Key is too short");
603
- }
604
590
  }
605
591
 
606
592
  // src/resources/usage.ts
@@ -1124,10 +1110,7 @@ var MarginFrontClient = class {
1124
1110
  validateApiKey(config.apiKey);
1125
1111
  this._apiKey = config.apiKey;
1126
1112
  const keyInfo = parseApiKey(config.apiKey);
1127
- if (!keyInfo) {
1128
- throw new ValidationError("Invalid API key format");
1129
- }
1130
- this._keyType = keyInfo.type;
1113
+ this._keyType = keyInfo?.type ?? "secret";
1131
1114
  this.http = new HttpClient(config);
1132
1115
  this.usage = new UsageResource(this.http);
1133
1116
  this.customers = new CustomersResource(this.http);
@@ -1140,15 +1123,10 @@ var MarginFrontClient = class {
1140
1123
  );
1141
1124
  }
1142
1125
  /**
1143
- * Assert that the current API key is a secret key
1144
- * @throws ValidationError if using a publishable key
1126
+ * No-op the server enforces key type for secret-only operations.
1127
+ * Kept so PortalSessionsResource can call it without breaking.
1145
1128
  */
1146
1129
  assertSecretKey() {
1147
- if (this._keyType !== "secret") {
1148
- throw new ValidationError(
1149
- "This operation requires a secret key (mf_sk_*). Publishable keys (mf_pk_*) cannot perform this action. Use your secret key on the server side only."
1150
- );
1151
- }
1152
1130
  }
1153
1131
  /**
1154
1132
  * Connect to the MarginFront API and verify credentials