learn-secrets-sdk 1.5.0 → 1.6.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.
@@ -30,28 +30,45 @@ function ensureLearnDir() {
30
30
  fs.mkdirSync(learnDir, { recursive: true, mode: 448 });
31
31
  }
32
32
  }
33
- function readCredentials() {
33
+ function readAllCredentials() {
34
34
  try {
35
35
  const credPath = getCredentialsPath();
36
36
  if (!fs.existsSync(credPath)) {
37
37
  return null;
38
38
  }
39
39
  const data = fs.readFileSync(credPath, "utf-8");
40
- const creds = JSON.parse(data);
41
- if (!creds.access_token || !creds.refresh_token || !creds.expires_at || !creds.user_id) {
42
- return null;
40
+ const store = JSON.parse(data);
41
+ if (store.access_token && !store["learn-secrets-sdk"] && !store["learn-auth-sdk"]) {
42
+ return {
43
+ "learn-secrets-sdk": store
44
+ };
43
45
  }
44
- return creds;
46
+ return store;
45
47
  } catch (err) {
46
48
  return null;
47
49
  }
48
50
  }
49
- function writeCredentials(creds) {
51
+ function writeAllCredentials(store) {
50
52
  ensureLearnDir();
51
53
  const credPath = getCredentialsPath();
52
- const data = JSON.stringify(creds, null, 2);
54
+ const data = JSON.stringify(store, null, 2);
53
55
  fs.writeFileSync(credPath, data, { mode: 384 });
54
56
  }
57
+ function readSDKCredentials(sdkName) {
58
+ const store = readAllCredentials();
59
+ if (!store) {
60
+ return null;
61
+ }
62
+ return store[sdkName] || null;
63
+ }
64
+ function writeSDKCredentials(sdkName, creds) {
65
+ const store = readAllCredentials() || {};
66
+ store[sdkName] = creds;
67
+ writeAllCredentials(store);
68
+ }
69
+ function readCredentials() {
70
+ return readSDKCredentials("learn-secrets-sdk");
71
+ }
55
72
  function deleteCredentials() {
56
73
  try {
57
74
  const credPath = getCredentialsPath();
@@ -66,11 +83,94 @@ function hasValidCredentials() {
66
83
  if (!creds) {
67
84
  return false;
68
85
  }
86
+ if (!creds.expires_at) {
87
+ return true;
88
+ }
69
89
  const expiresAt = new Date(creds.expires_at);
70
90
  const now = /* @__PURE__ */ new Date();
71
91
  return expiresAt > now;
72
92
  }
73
93
 
94
+ // src/machine-id.ts
95
+ import { exec } from "child_process";
96
+ import { promisify } from "util";
97
+ import { createHash } from "crypto";
98
+ import * as os2 from "os";
99
+ var execAsync = promisify(exec);
100
+ async function getMachineId() {
101
+ const platform2 = process.platform;
102
+ try {
103
+ let rawId;
104
+ if (platform2 === "darwin") {
105
+ rawId = await getMacOSId();
106
+ } else if (platform2 === "linux") {
107
+ rawId = await getLinuxId();
108
+ } else if (platform2 === "win32") {
109
+ rawId = await getWindowsId();
110
+ } else {
111
+ throw new Error(`Unsupported platform: ${platform2}`);
112
+ }
113
+ const machineId = createHash("sha256").update(rawId).digest("hex").substring(0, 32);
114
+ const fingerprintData = JSON.stringify({
115
+ machineId,
116
+ cpus: os2.cpus().length,
117
+ arch: os2.arch(),
118
+ platform: os2.platform(),
119
+ homedir: os2.homedir()
120
+ });
121
+ const fingerprint = createHash("sha256").update(fingerprintData).digest("hex");
122
+ return { machineId, fingerprint };
123
+ } catch (error) {
124
+ throw new Error(`Failed to get machine ID: ${error.message}`);
125
+ }
126
+ }
127
+ async function getMacOSId() {
128
+ try {
129
+ const { stdout: serial } = await execAsync(
130
+ `ioreg -l | grep IOPlatformSerialNumber | awk '{print $4}' | sed 's/"//g'`
131
+ );
132
+ const { stdout: hostname } = await execAsync("hostname");
133
+ const serialTrimmed = serial.trim();
134
+ const hostnameTrimmed = hostname.trim();
135
+ if (serialTrimmed && hostnameTrimmed) {
136
+ return `${serialTrimmed}-${hostnameTrimmed}`;
137
+ }
138
+ const { stdout: uuid } = await execAsync(
139
+ `ioreg -rd1 -c IOPlatformExpertDevice | grep IOPlatformUUID | awk '{print $3}' | sed 's/"//g'`
140
+ );
141
+ return uuid.trim();
142
+ } catch (error) {
143
+ throw new Error("Failed to get macOS machine ID");
144
+ }
145
+ }
146
+ async function getLinuxId() {
147
+ try {
148
+ const { stdout } = await execAsync(
149
+ "cat /etc/machine-id 2>/dev/null || cat /var/lib/dbus/machine-id 2>/dev/null"
150
+ );
151
+ const machineId = stdout.trim();
152
+ if (machineId) {
153
+ return machineId;
154
+ }
155
+ throw new Error("Machine ID file not found");
156
+ } catch (error) {
157
+ throw new Error("Failed to get Linux machine ID");
158
+ }
159
+ }
160
+ async function getWindowsId() {
161
+ try {
162
+ const { stdout } = await execAsync("wmic csproduct get uuid");
163
+ const lines = stdout.split("\n").map((l) => l.trim());
164
+ const uuid = lines.find((l) => l && l !== "UUID");
165
+ if (uuid) {
166
+ return uuid;
167
+ }
168
+ throw new Error("Failed to parse machine UUID from WMIC");
169
+ } catch (error) {
170
+ throw new Error("Failed to get Windows machine ID");
171
+ }
172
+ }
173
+
74
174
  // src/management.ts
75
175
  var SecretsManagement = class {
76
176
  constructor(options) {
@@ -82,17 +182,17 @@ var SecretsManagement = class {
82
182
  * Open a URL in the default browser
83
183
  */
84
184
  async openBrowser(url) {
85
- const { exec } = await import("child_process");
86
- const { promisify } = await import("util");
87
- const execAsync = promisify(exec);
88
- const platform = process.platform;
185
+ const { exec: exec2 } = await import("child_process");
186
+ const { promisify: promisify2 } = await import("util");
187
+ const execAsync2 = promisify2(exec2);
188
+ const platform2 = process.platform;
89
189
  try {
90
- if (platform === "darwin") {
91
- await execAsync(`open "${url}"`);
92
- } else if (platform === "win32") {
93
- await execAsync(`start "${url}"`);
190
+ if (platform2 === "darwin") {
191
+ await execAsync2(`open "${url}"`);
192
+ } else if (platform2 === "win32") {
193
+ await execAsync2(`start "${url}"`);
94
194
  } else {
95
- await execAsync(`xdg-open "${url}"`);
195
+ await execAsync2(`xdg-open "${url}"`);
96
196
  }
97
197
  } catch (err) {
98
198
  console.error("Failed to open browser:", err);
@@ -106,11 +206,39 @@ var SecretsManagement = class {
106
206
  return new Promise((resolve2) => setTimeout(resolve2, ms));
107
207
  }
108
208
  /**
109
- * Browser OAuth login flow
110
- * Opens browser for user to authorize, then stores credentials locally
209
+ * Browser OAuth login flow with 2FA
210
+ * Opens browser for user to authorize, then prompts for 2FA code
111
211
  */
112
212
  async login() {
213
+ const SDK_NAME = "learn-secrets-sdk";
113
214
  try {
215
+ console.log(`Authenticating with Learn (${SDK_NAME})...
216
+ `);
217
+ const machineInfo = await getMachineId();
218
+ const existingCreds = readSDKCredentials(SDK_NAME);
219
+ if (existingCreds && existingCreds.access_token) {
220
+ const checkResponse = await fetch(`${this.baseUrl}/api/auth/cli/check-authorization`, {
221
+ method: "POST",
222
+ headers: {
223
+ "Content-Type": "application/json"
224
+ },
225
+ body: JSON.stringify({
226
+ machine_id: machineInfo.machineId,
227
+ sdk_name: SDK_NAME
228
+ })
229
+ });
230
+ if (checkResponse.ok) {
231
+ const checkData = await checkResponse.json();
232
+ if (checkData.authorized) {
233
+ console.log("This machine is already authorized.");
234
+ console.log(`Logged in as: ${checkData.user_email || checkData.user_id}`);
235
+ return {
236
+ success: true,
237
+ user: checkData.user_email || checkData.user_id
238
+ };
239
+ }
240
+ }
241
+ }
114
242
  const deviceResponse = await fetch(`${this.baseUrl}/api/auth/cli/device`, {
115
243
  method: "POST",
116
244
  headers: {
@@ -124,12 +252,13 @@ var SecretsManagement = class {
124
252
  );
125
253
  }
126
254
  const deviceData = await deviceResponse.json();
127
- console.log("\nTo authorize this application, visit:");
128
- console.log(` ${deviceData.verification_uri}`);
129
- console.log("\nAnd enter the code:");
130
- console.log(` ${deviceData.user_code}`);
255
+ console.log("\nTo authorize this device:");
256
+ console.log(`1. Visit: ${deviceData.verification_uri}`);
257
+ console.log(`2. Enter code: ${deviceData.user_code}`);
258
+ console.log(`3. Complete 2FA verification`);
131
259
  console.log("\nOpening browser...\n");
132
260
  await this.openBrowser(deviceData.verification_uri_complete);
261
+ let deviceAuthorized = false;
133
262
  const interval = deviceData.interval * 1e3;
134
263
  const maxAttempts = Math.ceil(deviceData.expires_in / deviceData.interval);
135
264
  let attempts = 0;
@@ -149,28 +278,79 @@ var SecretsManagement = class {
149
278
  process.stdout.write(".");
150
279
  continue;
151
280
  }
152
- if (!tokenResponse.ok) {
153
- const errorData = await tokenResponse.json().catch(() => ({}));
154
- throw new SecretsSDKError(
155
- errorData.message || "Failed to exchange device code",
156
- tokenResponse.status
157
- );
281
+ if (tokenResponse.ok) {
282
+ deviceAuthorized = true;
283
+ break;
158
284
  }
159
- const tokenData = await tokenResponse.json();
160
- const expiresAt = new Date(Date.now() + tokenData.expires_in * 1e3);
161
- writeCredentials({
162
- access_token: tokenData.access_token,
163
- refresh_token: tokenData.refresh_token,
164
- expires_at: expiresAt.toISOString(),
165
- user_id: tokenData.user_id
285
+ }
286
+ if (!deviceAuthorized) {
287
+ throw new SecretsSDKError("Device authorization timeout", 408);
288
+ }
289
+ console.log("\n\nDevice authorized! Sending 2FA code...");
290
+ const tfaResponse = await fetch(`${this.baseUrl}/api/auth/2fa/send`, {
291
+ method: "POST",
292
+ headers: {
293
+ "Content-Type": "application/json"
294
+ },
295
+ body: JSON.stringify({
296
+ device_code: deviceData.device_code,
297
+ sdk_name: SDK_NAME,
298
+ machine_id: machineInfo.machineId
299
+ })
300
+ });
301
+ if (!tfaResponse.ok) {
302
+ const errorData = await tfaResponse.json().catch(() => ({}));
303
+ throw new SecretsSDKError(
304
+ errorData.message || "Failed to send 2FA code",
305
+ tfaResponse.status
306
+ );
307
+ }
308
+ console.log("2FA code sent to your email.\n");
309
+ const readline = await import("readline");
310
+ const rl = readline.createInterface({
311
+ input: process.stdin,
312
+ output: process.stdout
313
+ });
314
+ const tfaCode = await new Promise((resolve2) => {
315
+ rl.question("Enter 2FA code from email: ", (answer) => {
316
+ rl.close();
317
+ resolve2(answer);
166
318
  });
167
- console.log("\n\nAuthentication successful!");
168
- return {
169
- success: true,
170
- user: tokenData.user_id
171
- };
319
+ });
320
+ const verifyResponse = await fetch(`${this.baseUrl}/api/auth/2fa/verify`, {
321
+ method: "POST",
322
+ headers: {
323
+ "Content-Type": "application/json"
324
+ },
325
+ body: JSON.stringify({
326
+ device_code: deviceData.device_code,
327
+ code: tfaCode.trim()
328
+ })
329
+ });
330
+ if (!verifyResponse.ok) {
331
+ const errorData = await verifyResponse.json().catch(() => ({}));
332
+ throw new SecretsSDKError(
333
+ errorData.message || "2FA verification failed",
334
+ verifyResponse.status
335
+ );
172
336
  }
173
- throw new SecretsSDKError("Authorization timeout", 408);
337
+ const tokenData = await verifyResponse.json();
338
+ writeSDKCredentials(SDK_NAME, {
339
+ access_token: tokenData.access_token,
340
+ refresh_token: null,
341
+ expires_at: null,
342
+ user_id: tokenData.user_id,
343
+ machine_id: machineInfo.machineId,
344
+ sdk_name: SDK_NAME,
345
+ authorized_at: (/* @__PURE__ */ new Date()).toISOString()
346
+ });
347
+ console.log("\nAuthentication successful!");
348
+ console.log(`Logged in as: ${tokenData.user_email || tokenData.user_id}`);
349
+ console.log("This machine is now permanently authorized.\n");
350
+ return {
351
+ success: true,
352
+ user: tokenData.user_email || tokenData.user_id
353
+ };
174
354
  } catch (err) {
175
355
  if (err instanceof SecretsSDKError) {
176
356
  throw err;
@@ -203,10 +383,12 @@ var SecretsManagement = class {
203
383
  if (!creds) {
204
384
  throw new SecretsSDKError("Not authenticated. Run login() first.", 401);
205
385
  }
206
- const expiresAt = new Date(creds.expires_at);
207
- const now = /* @__PURE__ */ new Date();
208
- if (expiresAt <= now) {
209
- throw new SecretsSDKError("Token expired. Run login() again.", 401);
386
+ if (creds.expires_at) {
387
+ const expiresAt = new Date(creds.expires_at);
388
+ const now = /* @__PURE__ */ new Date();
389
+ if (expiresAt <= now) {
390
+ throw new SecretsSDKError("Token expired. Run login() again.", 401);
391
+ }
210
392
  }
211
393
  return creds.access_token;
212
394
  }
@@ -816,7 +998,7 @@ async function deployCommand() {
816
998
 
817
999
  // src/cli/index.ts
818
1000
  var program = new Command();
819
- program.name("learn-secrets").description("CLI tool for managing API secrets in static sites").version("1.5.0");
1001
+ program.name("learn-secrets").description("CLI tool for managing API secrets in static sites").version("1.6.0");
820
1002
  program.command("setup").description("Complete setup: create project, generate token, upload secrets").requiredOption("-e, --email <email>", "Your Learn account email").requiredOption("-p, --password <password>", "Your Learn account password").requiredOption("-o, --origin <domain>", "Your site origin (e.g., mysite.com)").option("--project-name <name>", 'Project name (default: "Project - <origin>")').option("--env <file>", "Path to .env file (default: .env)", ".env").option("--base-url <url>", "Custom base URL for API").option("-y, --yes", "Skip confirmation prompt").action(async (options) => {
821
1003
  await setupCommand(options);
822
1004
  });
package/dist/index.d.mts CHANGED
@@ -102,10 +102,18 @@ interface SyncResult {
102
102
  }
103
103
  interface CLICredentials {
104
104
  access_token: string;
105
- refresh_token: string;
106
- expires_at: string;
105
+ refresh_token: string | null;
106
+ expires_at: string | null;
107
107
  user_id: string;
108
+ machine_id?: string;
109
+ sdk_name?: string;
110
+ authorized_at?: string;
108
111
  }
112
+ interface SDKCredentialsStore {
113
+ 'learn-secrets-sdk'?: CLICredentials;
114
+ 'learn-auth-sdk'?: CLICredentials;
115
+ }
116
+ type SDKName = 'learn-secrets-sdk' | 'learn-auth-sdk';
109
117
 
110
118
  declare class SecretsSDK {
111
119
  private appId;
@@ -278,8 +286,8 @@ declare class SecretsManagement {
278
286
  */
279
287
  private sleep;
280
288
  /**
281
- * Browser OAuth login flow
282
- * Opens browser for user to authorize, then stores credentials locally
289
+ * Browser OAuth login flow with 2FA
290
+ * Opens browser for user to authorize, then prompts for 2FA code
283
291
  */
284
292
  login(): Promise<{
285
293
  success: boolean;
@@ -382,4 +390,43 @@ declare function loadSecretsConfig(configPath: string): {
382
390
  secrets: SecretConfig[];
383
391
  };
384
392
 
385
- export { type CLICredentials, InvalidTokenError, type MaskedSecret, OriginMismatchError, type ProxyRequest, type ProxyResponse, RateLimitError, type RateLimitInfo, type SecretConfig, SecretsManagement, type SecretsManagementOptions, SecretsSDK, SecretsSDKError, type SecretsSDKOptions, type SyncOptions, type SyncResult, loadSecretsConfig, resolveEnvInSecret, resolveEnvInSecrets, resolveEnvString };
393
+ /**
394
+ * Machine ID Generation
395
+ * Cross-platform machine identification for CLI authentication
396
+ */
397
+ interface MachineInfo {
398
+ machineId: string;
399
+ fingerprint: string;
400
+ }
401
+ /**
402
+ * Get a unique machine identifier
403
+ * This ID persists across reboots and SDK reinstalls
404
+ */
405
+ declare function getMachineId(): Promise<MachineInfo>;
406
+
407
+ /**
408
+ * Read all credentials from file (SDK-keyed format)
409
+ */
410
+ declare function readAllCredentials(): SDKCredentialsStore | null;
411
+ /**
412
+ * Write all credentials to file (SDK-keyed format)
413
+ */
414
+ declare function writeAllCredentials(store: SDKCredentialsStore): void;
415
+ /**
416
+ * Read credentials for a specific SDK
417
+ */
418
+ declare function readSDKCredentials(sdkName: SDKName): CLICredentials | null;
419
+ /**
420
+ * Write credentials for a specific SDK
421
+ */
422
+ declare function writeSDKCredentials(sdkName: SDKName, creds: CLICredentials): void;
423
+ /**
424
+ * Delete credentials for a specific SDK
425
+ */
426
+ declare function deleteSDKCredentials(sdkName: SDKName): void;
427
+ /**
428
+ * Check if credentials exist for a specific SDK
429
+ */
430
+ declare function hasSDKCredentials(sdkName: SDKName): boolean;
431
+
432
+ export { type CLICredentials, InvalidTokenError, type MachineInfo, type MaskedSecret, OriginMismatchError, type ProxyRequest, type ProxyResponse, RateLimitError, type RateLimitInfo, type SDKCredentialsStore, type SDKName, type SecretConfig, SecretsManagement, type SecretsManagementOptions, SecretsSDK, SecretsSDKError, type SecretsSDKOptions, type SyncOptions, type SyncResult, deleteSDKCredentials, getMachineId, hasSDKCredentials, loadSecretsConfig, readAllCredentials, readSDKCredentials, resolveEnvInSecret, resolveEnvInSecrets, resolveEnvString, writeAllCredentials, writeSDKCredentials };
package/dist/index.d.ts CHANGED
@@ -102,10 +102,18 @@ interface SyncResult {
102
102
  }
103
103
  interface CLICredentials {
104
104
  access_token: string;
105
- refresh_token: string;
106
- expires_at: string;
105
+ refresh_token: string | null;
106
+ expires_at: string | null;
107
107
  user_id: string;
108
+ machine_id?: string;
109
+ sdk_name?: string;
110
+ authorized_at?: string;
108
111
  }
112
+ interface SDKCredentialsStore {
113
+ 'learn-secrets-sdk'?: CLICredentials;
114
+ 'learn-auth-sdk'?: CLICredentials;
115
+ }
116
+ type SDKName = 'learn-secrets-sdk' | 'learn-auth-sdk';
109
117
 
110
118
  declare class SecretsSDK {
111
119
  private appId;
@@ -278,8 +286,8 @@ declare class SecretsManagement {
278
286
  */
279
287
  private sleep;
280
288
  /**
281
- * Browser OAuth login flow
282
- * Opens browser for user to authorize, then stores credentials locally
289
+ * Browser OAuth login flow with 2FA
290
+ * Opens browser for user to authorize, then prompts for 2FA code
283
291
  */
284
292
  login(): Promise<{
285
293
  success: boolean;
@@ -382,4 +390,43 @@ declare function loadSecretsConfig(configPath: string): {
382
390
  secrets: SecretConfig[];
383
391
  };
384
392
 
385
- export { type CLICredentials, InvalidTokenError, type MaskedSecret, OriginMismatchError, type ProxyRequest, type ProxyResponse, RateLimitError, type RateLimitInfo, type SecretConfig, SecretsManagement, type SecretsManagementOptions, SecretsSDK, SecretsSDKError, type SecretsSDKOptions, type SyncOptions, type SyncResult, loadSecretsConfig, resolveEnvInSecret, resolveEnvInSecrets, resolveEnvString };
393
+ /**
394
+ * Machine ID Generation
395
+ * Cross-platform machine identification for CLI authentication
396
+ */
397
+ interface MachineInfo {
398
+ machineId: string;
399
+ fingerprint: string;
400
+ }
401
+ /**
402
+ * Get a unique machine identifier
403
+ * This ID persists across reboots and SDK reinstalls
404
+ */
405
+ declare function getMachineId(): Promise<MachineInfo>;
406
+
407
+ /**
408
+ * Read all credentials from file (SDK-keyed format)
409
+ */
410
+ declare function readAllCredentials(): SDKCredentialsStore | null;
411
+ /**
412
+ * Write all credentials to file (SDK-keyed format)
413
+ */
414
+ declare function writeAllCredentials(store: SDKCredentialsStore): void;
415
+ /**
416
+ * Read credentials for a specific SDK
417
+ */
418
+ declare function readSDKCredentials(sdkName: SDKName): CLICredentials | null;
419
+ /**
420
+ * Write credentials for a specific SDK
421
+ */
422
+ declare function writeSDKCredentials(sdkName: SDKName, creds: CLICredentials): void;
423
+ /**
424
+ * Delete credentials for a specific SDK
425
+ */
426
+ declare function deleteSDKCredentials(sdkName: SDKName): void;
427
+ /**
428
+ * Check if credentials exist for a specific SDK
429
+ */
430
+ declare function hasSDKCredentials(sdkName: SDKName): boolean;
431
+
432
+ export { type CLICredentials, InvalidTokenError, type MachineInfo, type MaskedSecret, OriginMismatchError, type ProxyRequest, type ProxyResponse, RateLimitError, type RateLimitInfo, type SDKCredentialsStore, type SDKName, type SecretConfig, SecretsManagement, type SecretsManagementOptions, SecretsSDK, SecretsSDKError, type SecretsSDKOptions, type SyncOptions, type SyncResult, deleteSDKCredentials, getMachineId, hasSDKCredentials, loadSecretsConfig, readAllCredentials, readSDKCredentials, resolveEnvInSecret, resolveEnvInSecrets, resolveEnvString, writeAllCredentials, writeSDKCredentials };
@@ -1,7 +1,11 @@
1
- "use strict";var SecretsSDK=(()=>{var U=Object.create;var b=Object.defineProperty;var N=Object.getOwnPropertyDescriptor;var F=Object.getOwnPropertyNames;var z=Object.getPrototypeOf,J=Object.prototype.hasOwnProperty;var l=(r=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(r,{get:(e,t)=>(typeof require<"u"?require:e)[t]}):r)(function(r){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+r+'" is not supported')});var K=(r,e)=>()=>(r&&(e=r(r=0)),e);var O=(r,e)=>{for(var t in e)b(r,t,{get:e[t],enumerable:!0})},D=(r,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of F(e))!J.call(r,n)&&n!==t&&b(r,n,{get:()=>e[n],enumerable:!(s=N(e,n))||s.enumerable});return r};var w=(r,e,t)=>(t=r!=null?U(z(r)):{},D(e||!r||!r.__esModule?b(t,"default",{value:r,enumerable:!0}):t,r)),H=r=>D(b({},"__esModule",{value:!0}),r);var j={};O(j,{loadSecretsConfig:()=>I,resolveEnvInSecret:()=>x,resolveEnvInSecrets:()=>k,resolveEnvString:()=>d});function d(r){return r.replace(/\$\{([A-Z0-9_]+)\}|\$([A-Z0-9_]+)/g,(e,t,s)=>{let n=t||s,a=process.env[n];if(a===void 0)throw new Error(`Environment variable ${n} is not defined`);return a})}function x(r){let e={name:d(r.name),provider:d(r.provider),api_key:d(r.api_key)};return r.base_url&&(e.base_url=d(r.base_url)),r.auth_header&&(e.auth_header=d(r.auth_header)),r.auth_prefix&&(e.auth_prefix=d(r.auth_prefix)),e}function k(r){return r.map(x)}function I(r){let e=l("fs"),s=l("path").resolve(r);if(!e.existsSync(s))throw new Error(`Config file not found: ${s}`);let n=e.readFileSync(s,"utf-8"),a=JSON.parse(n);if(!a.version)throw new Error('Config file missing "version" field');if(!a.project)throw new Error('Config file missing "project" field');if(!Array.isArray(a.secrets))throw new Error('Config file missing "secrets" array');let i=k(a.secrets);return{version:a.version,project:a.project,secrets:i}}var P=K(()=>{"use strict"});var G={};O(G,{InvalidTokenError:()=>h,OriginMismatchError:()=>m,RateLimitError:()=>f,SecretsManagement:()=>_,SecretsSDK:()=>S,SecretsSDKError:()=>o,loadSecretsConfig:()=>I,resolveEnvInSecret:()=>x,resolveEnvInSecrets:()=>k,resolveEnvString:()=>d});var o=class extends Error{constructor(t,s,n){super(t);this.status=s;this.response=n;this.name="SecretsSDKError"}},m=class extends o{constructor(e="Origin not allowed for this token"){super(e,403),this.name="OriginMismatchError"}},f=class extends o{constructor(e="Rate limit exceeded",t=60,s=0,n=100){super(e,429),this.name="RateLimitError",this.retryAfter=t,this.remaining=s,this.limit=n}},h=class extends o{constructor(e="Invalid or expired token"){super(e,401),this.name="InvalidTokenError"}};var S=class{constructor(e={}){this.rateLimitInfo=null;this.appId=e.appId||null,this.token=e.token||e.sessionToken||null,this.baseUrl=e.baseUrl||"https://ctklearn.carsontkempf.workers.dev",this.timeout=e.timeout||3e4,this.retryOn429=e.retryOn429!==void 0?e.retryOn429:!0,this.zeroConfigMode=!this.appId&&!this.token}getUsage(){return this.rateLimitInfo}parseRateLimitHeaders(e){let t=e.get("X-RateLimit-Limit"),s=e.get("X-RateLimit-Remaining"),n=e.get("X-RateLimit-Reset");t&&s&&n&&(this.rateLimitInfo={limit:parseInt(t),remaining:parseInt(s),reset:parseInt(n)})}sleep(e){return new Promise(t=>setTimeout(t,e))}async fetchWithTimeout(e,t,s){let n=new AbortController,a=setTimeout(()=>n.abort(),s);try{return await fetch(e,{...t,signal:n.signal})}finally{clearTimeout(a)}}async call(e,t,s={}){let n=0,a=this.retryOn429?3:0;for(;;)try{let i=this.zeroConfigMode?`${this.baseUrl}/api/sdk/proxy`:`${this.baseUrl}/api/sdk/${this.appId}/proxy`,p={"Content-Type":"application/json"};!this.zeroConfigMode&&this.token&&(p.Authorization=`Bearer ${this.token}`);let u=await this.fetchWithTimeout(i,{method:"POST",headers:p,body:JSON.stringify({keyName:e,endpoint:t,method:s.method||"GET",body:s.body,headers:s.headers})},this.timeout);this.parseRateLimitHeaders(u.headers);let g=await u.json();if(!u.ok){let y=g.data?.message||g?.message||`Request failed with status ${u.status}`;if(u.status===403&&y.includes("Origin"))throw new m(y);if(u.status===401)throw new h(y);if(u.status===429){let $=this.rateLimitInfo?this.rateLimitInfo.reset-Math.floor(Date.now()/1e3):60,q=new f(y,$,this.rateLimitInfo?.remaining||0,this.rateLimitInfo?.limit||100);if(this.retryOn429&&n<a){n++;let M=Math.min(1e3*Math.pow(2,n-1),8e3);await this.sleep(M);continue}throw q}throw new o(y,u.status,g)}return g.data}catch(i){throw i instanceof o?i:i instanceof Error&&i.name==="AbortError"?new o("Request timeout",408):new o(i instanceof Error?i.message:"Unknown error occurred",500)}}async get(e,t,s){return this.call(e,t,{method:"GET",headers:s})}async post(e,t,s,n){return this.call(e,t,{method:"POST",body:s,headers:n})}async put(e,t,s,n){return this.call(e,t,{method:"PUT",body:s,headers:n})}async delete(e,t,s){return this.call(e,t,{method:"DELETE",headers:s})}async patch(e,t,s,n){return this.call(e,t,{method:"PATCH",body:s,headers:n})}setAppId(e){this.appId=e}async secretsRequest(e,t){if(!this.appId)throw new o("App ID required for secrets management. Call setAppId() first.",400);let s=await this.fetchWithTimeout(`${this.baseUrl}/api/projects/${this.appId}/secrets/manage?action=${e}`,{method:"POST",headers:{"Content-Type":"application/json"},body:t?JSON.stringify(t):void 0},this.timeout);if(!s.ok){let n=await s.json().catch(()=>({}));throw new o(n.message||`Secrets management failed: ${s.statusText}`,s.status,n)}return s.json()}async listSecrets(){return this.secretsRequest("list")}async createSecret(e){return this.secretsRequest("create",e)}async updateSecret(e){return this.secretsRequest("update",e)}async deleteSecret(e){return this.secretsRequest("delete",{name:e})}async syncEnv(e,t){return this.secretsRequest("sync",{secrets:e,provider:t})}};var c=w(l("fs")),v=w(l("path")),T=w(l("os"));function R(){let r=T.homedir(),e=v.join(r,".learn");return v.join(e,"credentials.json")}function B(){let r=T.homedir(),e=v.join(r,".learn");c.existsSync(e)||c.mkdirSync(e,{recursive:!0,mode:448})}function C(){try{let r=R();if(!c.existsSync(r))return null;let e=c.readFileSync(r,"utf-8"),t=JSON.parse(e);return!t.access_token||!t.refresh_token||!t.expires_at||!t.user_id?null:t}catch{return null}}function E(r){B();let e=R(),t=JSON.stringify(r,null,2);c.writeFileSync(e,t,{mode:384})}function A(){try{let r=R();c.existsSync(r)&&c.unlinkSync(r)}catch{}}function L(){let r=C();return r?new Date(r.expires_at)>new Date:!1}var _=class{constructor(e){this.appId=e.appId,this.baseUrl=e.baseUrl||"https://ctklearn.carsontkempf.workers.dev",this.timeout=e.timeout||3e4}async openBrowser(e){let{exec:t}=await import("child_process"),{promisify:s}=await import("util"),n=s(t),a=process.platform;try{a==="darwin"?await n(`open "${e}"`):a==="win32"?await n(`start "${e}"`):await n(`xdg-open "${e}"`)}catch(i){throw console.error("Failed to open browser:",i),new o("Failed to open browser",500)}}sleep(e){return new Promise(t=>setTimeout(t,e))}async login(){try{let e=await fetch(`${this.baseUrl}/api/auth/cli/device`,{method:"POST",headers:{"Content-Type":"application/json"}});if(!e.ok)throw new o("Failed to generate device code",e.status);let t=await e.json();console.log(`
2
- To authorize this application, visit:`),console.log(` ${t.verification_uri}`),console.log(`
3
- And enter the code:`),console.log(` ${t.user_code}`),console.log(`
1
+ "use strict";var SecretsSDK=(()=>{var ee=Object.create;var D=Object.defineProperty;var te=Object.getOwnPropertyDescriptor;var re=Object.getOwnPropertyNames;var se=Object.getPrototypeOf,ne=Object.prototype.hasOwnProperty;var c=(t=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(t,{get:(e,r)=>(typeof require<"u"?require:e)[r]}):t)(function(t){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+t+'" is not supported')});var ie=(t,e)=>()=>(t&&(e=t(t=0)),e);var W=(t,e)=>{for(var r in e)D(t,r,{get:e[r],enumerable:!0})},B=(t,e,r,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of re(e))!ne.call(t,n)&&n!==r&&D(t,n,{get:()=>e[n],enumerable:!(s=te(e,n))||s.enumerable});return t};var w=(t,e,r)=>(r=t!=null?ee(se(t)):{},B(e||!t||!t.__esModule?D(r,"default",{value:t,enumerable:!0}):r,t)),oe=t=>B(D({},"__esModule",{value:!0}),t);var Y={};W(Y,{loadSecretsConfig:()=>z,resolveEnvInSecret:()=>L,resolveEnvInSecrets:()=>$,resolveEnvString:()=>m});function m(t){return t.replace(/\$\{([A-Z0-9_]+)\}|\$([A-Z0-9_]+)/g,(e,r,s)=>{let n=r||s,i=process.env[n];if(i===void 0)throw new Error(`Environment variable ${n} is not defined`);return i})}function L(t){let e={name:m(t.name),provider:m(t.provider),api_key:m(t.api_key)};return t.base_url&&(e.base_url=m(t.base_url)),t.auth_header&&(e.auth_header=m(t.auth_header)),t.auth_prefix&&(e.auth_prefix=m(t.auth_prefix)),e}function $(t){return t.map(L)}function z(t){let e=c("fs"),s=c("path").resolve(t);if(!e.existsSync(s))throw new Error(`Config file not found: ${s}`);let n=e.readFileSync(s,"utf-8"),i=JSON.parse(n);if(!i.version)throw new Error('Config file missing "version" field');if(!i.project)throw new Error('Config file missing "project" field');if(!Array.isArray(i.secrets))throw new Error('Config file missing "secrets" array');let a=$(i.secrets);return{version:i.version,project:i.project,secrets:a}}var J=ie(()=>{"use strict"});var ue={};W(ue,{InvalidTokenError:()=>b,OriginMismatchError:()=>v,RateLimitError:()=>x,SecretsManagement:()=>j,SecretsSDK:()=>R,SecretsSDKError:()=>o,deleteSDKCredentials:()=>G,getMachineId:()=>A,hasSDKCredentials:()=>V,loadSecretsConfig:()=>z,readAllCredentials:()=>C,readSDKCredentials:()=>k,resolveEnvInSecret:()=>L,resolveEnvInSecrets:()=>$,resolveEnvString:()=>m,writeAllCredentials:()=>O,writeSDKCredentials:()=>E});var o=class extends Error{constructor(r,s,n){super(r);this.status=s;this.response=n;this.name="SecretsSDKError"}},v=class extends o{constructor(e="Origin not allowed for this token"){super(e,403),this.name="OriginMismatchError"}},x=class extends o{constructor(e="Rate limit exceeded",r=60,s=0,n=100){super(e,429),this.name="RateLimitError",this.retryAfter=r,this.remaining=s,this.limit=n}},b=class extends o{constructor(e="Invalid or expired token"){super(e,401),this.name="InvalidTokenError"}};var R=class{constructor(e={}){this.rateLimitInfo=null;this.appId=e.appId||null,this.token=e.token||e.sessionToken||null,this.baseUrl=e.baseUrl||"https://ctklearn.carsontkempf.workers.dev",this.timeout=e.timeout||3e4,this.retryOn429=e.retryOn429!==void 0?e.retryOn429:!0,this.zeroConfigMode=!this.appId&&!this.token}getUsage(){return this.rateLimitInfo}parseRateLimitHeaders(e){let r=e.get("X-RateLimit-Limit"),s=e.get("X-RateLimit-Remaining"),n=e.get("X-RateLimit-Reset");r&&s&&n&&(this.rateLimitInfo={limit:parseInt(r),remaining:parseInt(s),reset:parseInt(n)})}sleep(e){return new Promise(r=>setTimeout(r,e))}async fetchWithTimeout(e,r,s){let n=new AbortController,i=setTimeout(()=>n.abort(),s);try{return await fetch(e,{...r,signal:n.signal})}finally{clearTimeout(i)}}async call(e,r,s={}){let n=0,i=this.retryOn429?3:0;for(;;)try{let a=this.zeroConfigMode?`${this.baseUrl}/api/sdk/proxy`:`${this.baseUrl}/api/sdk/${this.appId}/proxy`,h={"Content-Type":"application/json"};!this.zeroConfigMode&&this.token&&(h.Authorization=`Bearer ${this.token}`);let u=await this.fetchWithTimeout(a,{method:"POST",headers:h,body:JSON.stringify({keyName:e,endpoint:r,method:s.method||"GET",body:s.body,headers:s.headers})},this.timeout);this.parseRateLimitHeaders(u.headers);let f=await u.json();if(!u.ok){let p=f.data?.message||f?.message||`Request failed with status ${u.status}`;if(u.status===403&&p.includes("Origin"))throw new v(p);if(u.status===401)throw new b(p);if(u.status===429){let H=this.rateLimitInfo?this.rateLimitInfo.reset-Math.floor(Date.now()/1e3):60,I=new x(p,H,this.rateLimitInfo?.remaining||0,this.rateLimitInfo?.limit||100);if(this.retryOn429&&n<i){n++;let M=Math.min(1e3*Math.pow(2,n-1),8e3);await this.sleep(M);continue}throw I}throw new o(p,u.status,f)}return f.data}catch(a){throw a instanceof o?a:a instanceof Error&&a.name==="AbortError"?new o("Request timeout",408):new o(a instanceof Error?a.message:"Unknown error occurred",500)}}async get(e,r,s){return this.call(e,r,{method:"GET",headers:s})}async post(e,r,s,n){return this.call(e,r,{method:"POST",body:s,headers:n})}async put(e,r,s,n){return this.call(e,r,{method:"PUT",body:s,headers:n})}async delete(e,r,s){return this.call(e,r,{method:"DELETE",headers:s})}async patch(e,r,s,n){return this.call(e,r,{method:"PATCH",body:s,headers:n})}setAppId(e){this.appId=e}async secretsRequest(e,r){if(!this.appId)throw new o("App ID required for secrets management. Call setAppId() first.",400);let s=await this.fetchWithTimeout(`${this.baseUrl}/api/projects/${this.appId}/secrets/manage?action=${e}`,{method:"POST",headers:{"Content-Type":"application/json"},body:r?JSON.stringify(r):void 0},this.timeout);if(!s.ok){let n=await s.json().catch(()=>({}));throw new o(n.message||`Secrets management failed: ${s.statusText}`,s.status,n)}return s.json()}async listSecrets(){return this.secretsRequest("list")}async createSecret(e){return this.secretsRequest("create",e)}async updateSecret(e){return this.secretsRequest("update",e)}async deleteSecret(e){return this.secretsRequest("delete",{name:e})}async syncEnv(e,r){return this.secretsRequest("sync",{secrets:e,provider:r})}};var d=w(c("fs")),P=w(c("path")),K=w(c("os"));function U(){let t=K.homedir(),e=P.join(t,".learn");return P.join(e,"credentials.json")}function ae(){let t=K.homedir(),e=P.join(t,".learn");d.existsSync(e)||d.mkdirSync(e,{recursive:!0,mode:448})}function C(){try{let t=U();if(!d.existsSync(t))return null;let e=d.readFileSync(t,"utf-8"),r=JSON.parse(e);return r.access_token&&!r["learn-secrets-sdk"]&&!r["learn-auth-sdk"]?{"learn-secrets-sdk":r}:r}catch{return null}}function O(t){ae();let e=U(),r=JSON.stringify(t,null,2);d.writeFileSync(e,r,{mode:384})}function k(t){let e=C();return e&&e[t]||null}function E(t,e){let r=C()||{};r[t]=e,O(r)}function G(t){let e=C();e&&(delete e[t],Object.keys(e).length===0?N():O(e))}function q(){return k("learn-secrets-sdk")}function N(){try{let t=U();d.existsSync(t)&&d.unlinkSync(t)}catch{}}function V(t){let e=k(t);return e!==null&&!!e.access_token}function X(){let t=q();return t?t.expires_at?new Date(t.expires_at)>new Date:!0:!1}var Z=c("child_process"),Q=c("util"),F=c("crypto"),g=w(c("os")),_=(0,Q.promisify)(Z.exec);async function A(){let t=process.platform;try{let e;if(t==="darwin")e=await ce();else if(t==="linux")e=await de();else if(t==="win32")e=await le();else throw new Error(`Unsupported platform: ${t}`);let r=(0,F.createHash)("sha256").update(e).digest("hex").substring(0,32),s=JSON.stringify({machineId:r,cpus:g.cpus().length,arch:g.arch(),platform:g.platform(),homedir:g.homedir()}),n=(0,F.createHash)("sha256").update(s).digest("hex");return{machineId:r,fingerprint:n}}catch(e){throw new Error(`Failed to get machine ID: ${e.message}`)}}async function ce(){try{let{stdout:t}=await _(`ioreg -l | grep IOPlatformSerialNumber | awk '{print $4}' | sed 's/"//g'`),{stdout:e}=await _("hostname"),r=t.trim(),s=e.trim();if(r&&s)return`${r}-${s}`;let{stdout:n}=await _(`ioreg -rd1 -c IOPlatformExpertDevice | grep IOPlatformUUID | awk '{print $3}' | sed 's/"//g'`);return n.trim()}catch{throw new Error("Failed to get macOS machine ID")}}async function de(){try{let{stdout:t}=await _("cat /etc/machine-id 2>/dev/null || cat /var/lib/dbus/machine-id 2>/dev/null"),e=t.trim();if(e)return e;throw new Error("Machine ID file not found")}catch{throw new Error("Failed to get Linux machine ID")}}async function le(){try{let{stdout:t}=await _("wmic csproduct get uuid"),r=t.split(`
2
+ `).map(s=>s.trim()).find(s=>s&&s!=="UUID");if(r)return r;throw new Error("Failed to parse machine UUID from WMIC")}catch{throw new Error("Failed to get Windows machine ID")}}var j=class{constructor(e){this.appId=e.appId,this.baseUrl=e.baseUrl||"https://ctklearn.carsontkempf.workers.dev",this.timeout=e.timeout||3e4}async openBrowser(e){let{exec:r}=await import("child_process"),{promisify:s}=await import("util"),n=s(r),i=process.platform;try{i==="darwin"?await n(`open "${e}"`):i==="win32"?await n(`start "${e}"`):await n(`xdg-open "${e}"`)}catch(a){throw console.error("Failed to open browser:",a),new o("Failed to open browser",500)}}sleep(e){return new Promise(r=>setTimeout(r,e))}async login(){let e="learn-secrets-sdk";try{console.log(`Authenticating with Learn (${e})...
3
+ `);let r=await A(),s=k(e);if(s&&s.access_token){let l=await fetch(`${this.baseUrl}/api/auth/cli/check-authorization`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({machine_id:r.machineId,sdk_name:e})});if(l.ok){let y=await l.json();if(y.authorized)return console.log("This machine is already authorized."),console.log(`Logged in as: ${y.user_email||y.user_id}`),{success:!0,user:y.user_email||y.user_id}}}let n=await fetch(`${this.baseUrl}/api/auth/cli/device`,{method:"POST",headers:{"Content-Type":"application/json"}});if(!n.ok)throw new o("Failed to generate device code",n.status);let i=await n.json();console.log(`
4
+ To authorize this device:`),console.log(`1. Visit: ${i.verification_uri}`),console.log(`2. Enter code: ${i.user_code}`),console.log("3. Complete 2FA verification"),console.log(`
4
5
  Opening browser...
5
- `),await this.openBrowser(t.verification_uri_complete);let s=t.interval*1e3,n=Math.ceil(t.expires_in/t.interval),a=0;for(;a<n;){await this.sleep(s),a++;let i=await fetch(`${this.baseUrl}/api/auth/cli/token`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({device_code:t.device_code})});if(i.status===202){process.stdout.write(".");continue}if(!i.ok){let g=await i.json().catch(()=>({}));throw new o(g.message||"Failed to exchange device code",i.status)}let p=await i.json(),u=new Date(Date.now()+p.expires_in*1e3);return E({access_token:p.access_token,refresh_token:p.refresh_token,expires_at:u.toISOString(),user_id:p.user_id}),console.log(`
6
+ `),await this.openBrowser(i.verification_uri_complete);let a=!1,h=i.interval*1e3,u=Math.ceil(i.expires_in/i.interval),f=0;for(;f<u;){await this.sleep(h),f++;let l=await fetch(`${this.baseUrl}/api/auth/cli/token`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({device_code:i.device_code})});if(l.status===202){process.stdout.write(".");continue}if(l.ok){a=!0;break}}if(!a)throw new o("Device authorization timeout",408);console.log(`
6
7
 
7
- Authentication successful!`),{success:!0,user:p.user_id}}throw new o("Authorization timeout",408)}catch(e){throw e instanceof o?e:new o(e.message||"Login failed",e.status||500)}}async isAuthenticated(){return L()}logout(){A(),console.log("Logged out successfully")}getAccessToken(){let e=C();if(!e)throw new o("Not authenticated. Run login() first.",401);if(new Date(e.expires_at)<=new Date)throw new o("Token expired. Run login() again.",401);return e.access_token}async request(e,t,s){let n=this.getAccessToken(),a={method:e,headers:{"Content-Type":"application/json",Authorization:`Bearer ${n}`}};s&&(a.body=JSON.stringify(s));let i=await fetch(`${this.baseUrl}${t}`,a);if(!i.ok){let p=await i.json().catch(()=>({}));throw new o(p.message||`Request failed: ${i.statusText}`,i.status,p)}return i.json()}async list(){return(await this.request("GET",`/api/projects/${this.appId}/secrets`)).secrets}async sync(e,t){return await this.request("POST",`/api/projects/${this.appId}/secrets/sync`,{secrets:e,options:{deleteMissing:t?.deleteMissing??!1,dryRun:t?.dryRun??!1}})}async importSecrets(e){return this.request("POST",`/api/projects/${this.appId}/import-secrets`,{version:e.version||"1.0",origins:e.origins,secrets:e.secrets})}async importFromFile(e){let{loadSecretsConfig:t}=await Promise.resolve().then(()=>(P(),j)),s=t(e);return this.importSecrets(s)}};P();return H(G);})();
8
+ Device authorized! Sending 2FA code...`);let p=await fetch(`${this.baseUrl}/api/auth/2fa/send`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({device_code:i.device_code,sdk_name:e,machine_id:r.machineId})});if(!p.ok){let l=await p.json().catch(()=>({}));throw new o(l.message||"Failed to send 2FA code",p.status)}console.log(`2FA code sent to your email.
9
+ `);let I=(await import("readline")).createInterface({input:process.stdin,output:process.stdout}),M=await new Promise(l=>{I.question("Enter 2FA code from email: ",y=>{I.close(),l(y)})}),T=await fetch(`${this.baseUrl}/api/auth/2fa/verify`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({device_code:i.device_code,code:M.trim()})});if(!T.ok){let l=await T.json().catch(()=>({}));throw new o(l.message||"2FA verification failed",T.status)}let S=await T.json();return E(e,{access_token:S.access_token,refresh_token:null,expires_at:null,user_id:S.user_id,machine_id:r.machineId,sdk_name:e,authorized_at:new Date().toISOString()}),console.log(`
10
+ Authentication successful!`),console.log(`Logged in as: ${S.user_email||S.user_id}`),console.log(`This machine is now permanently authorized.
11
+ `),{success:!0,user:S.user_email||S.user_id}}catch(r){throw r instanceof o?r:new o(r.message||"Login failed",r.status||500)}}async isAuthenticated(){return X()}logout(){N(),console.log("Logged out successfully")}getAccessToken(){let e=q();if(!e)throw new o("Not authenticated. Run login() first.",401);if(e.expires_at&&new Date(e.expires_at)<=new Date)throw new o("Token expired. Run login() again.",401);return e.access_token}async request(e,r,s){let n=this.getAccessToken(),i={method:e,headers:{"Content-Type":"application/json",Authorization:`Bearer ${n}`}};s&&(i.body=JSON.stringify(s));let a=await fetch(`${this.baseUrl}${r}`,i);if(!a.ok){let h=await a.json().catch(()=>({}));throw new o(h.message||`Request failed: ${a.statusText}`,a.status,h)}return a.json()}async list(){return(await this.request("GET",`/api/projects/${this.appId}/secrets`)).secrets}async sync(e,r){return await this.request("POST",`/api/projects/${this.appId}/secrets/sync`,{secrets:e,options:{deleteMissing:r?.deleteMissing??!1,dryRun:r?.dryRun??!1}})}async importSecrets(e){return this.request("POST",`/api/projects/${this.appId}/import-secrets`,{version:e.version||"1.0",origins:e.origins,secrets:e.secrets})}async importFromFile(e){let{loadSecretsConfig:r}=await Promise.resolve().then(()=>(J(),Y)),s=r(e);return this.importSecrets(s)}};J();return oe(ue);})();
package/dist/index.mjs CHANGED
@@ -1,7 +1,11 @@
1
- import{a as P,b as C,c as O,d as D}from"./chunk-AS6G7JYX.mjs";var i=class extends Error{constructor(t,r,s){super(t);this.status=r;this.response=s;this.name="SecretsSDKError"}},m=class extends i{constructor(e="Origin not allowed for this token"){super(e,403),this.name="OriginMismatchError"}},g=class extends i{constructor(e="Rate limit exceeded",t=60,r=0,s=100){super(e,429),this.name="RateLimitError",this.retryAfter=t,this.remaining=r,this.limit=s}},h=class extends i{constructor(e="Invalid or expired token"){super(e,401),this.name="InvalidTokenError"}};var y=class{constructor(e={}){this.rateLimitInfo=null;this.appId=e.appId||null,this.token=e.token||e.sessionToken||null,this.baseUrl=e.baseUrl||"https://ctklearn.carsontkempf.workers.dev",this.timeout=e.timeout||3e4,this.retryOn429=e.retryOn429!==void 0?e.retryOn429:!0,this.zeroConfigMode=!this.appId&&!this.token}getUsage(){return this.rateLimitInfo}parseRateLimitHeaders(e){let t=e.get("X-RateLimit-Limit"),r=e.get("X-RateLimit-Remaining"),s=e.get("X-RateLimit-Reset");t&&r&&s&&(this.rateLimitInfo={limit:parseInt(t),remaining:parseInt(r),reset:parseInt(s)})}sleep(e){return new Promise(t=>setTimeout(t,e))}async fetchWithTimeout(e,t,r){let s=new AbortController,a=setTimeout(()=>s.abort(),r);try{return await fetch(e,{...t,signal:s.signal})}finally{clearTimeout(a)}}async call(e,t,r={}){let s=0,a=this.retryOn429?3:0;for(;;)try{let n=this.zeroConfigMode?`${this.baseUrl}/api/sdk/proxy`:`${this.baseUrl}/api/sdk/${this.appId}/proxy`,p={"Content-Type":"application/json"};!this.zeroConfigMode&&this.token&&(p.Authorization=`Bearer ${this.token}`);let u=await this.fetchWithTimeout(n,{method:"POST",headers:p,body:JSON.stringify({keyName:e,endpoint:t,method:r.method||"GET",body:r.body,headers:r.headers})},this.timeout);this.parseRateLimitHeaders(u.headers);let d=await u.json();if(!u.ok){let l=d.data?.message||d?.message||`Request failed with status ${u.status}`;if(u.status===403&&l.includes("Origin"))throw new m(l);if(u.status===401)throw new h(l);if(u.status===429){let R=this.rateLimitInfo?this.rateLimitInfo.reset-Math.floor(Date.now()/1e3):60,_=new g(l,R,this.rateLimitInfo?.remaining||0,this.rateLimitInfo?.limit||100);if(this.retryOn429&&s<a){s++;let I=Math.min(1e3*Math.pow(2,s-1),8e3);await this.sleep(I);continue}throw _}throw new i(l,u.status,d)}return d.data}catch(n){throw n instanceof i?n:n instanceof Error&&n.name==="AbortError"?new i("Request timeout",408):new i(n instanceof Error?n.message:"Unknown error occurred",500)}}async get(e,t,r){return this.call(e,t,{method:"GET",headers:r})}async post(e,t,r,s){return this.call(e,t,{method:"POST",body:r,headers:s})}async put(e,t,r,s){return this.call(e,t,{method:"PUT",body:r,headers:s})}async delete(e,t,r){return this.call(e,t,{method:"DELETE",headers:r})}async patch(e,t,r,s){return this.call(e,t,{method:"PATCH",body:r,headers:s})}setAppId(e){this.appId=e}async secretsRequest(e,t){if(!this.appId)throw new i("App ID required for secrets management. Call setAppId() first.",400);let r=await this.fetchWithTimeout(`${this.baseUrl}/api/projects/${this.appId}/secrets/manage?action=${e}`,{method:"POST",headers:{"Content-Type":"application/json"},body:t?JSON.stringify(t):void 0},this.timeout);if(!r.ok){let s=await r.json().catch(()=>({}));throw new i(s.message||`Secrets management failed: ${r.statusText}`,r.status,s)}return r.json()}async listSecrets(){return this.secretsRequest("list")}async createSecret(e){return this.secretsRequest("create",e)}async updateSecret(e){return this.secretsRequest("update",e)}async deleteSecret(e){return this.secretsRequest("delete",{name:e})}async syncEnv(e,t){return this.secretsRequest("sync",{secrets:e,provider:t})}};import*as c from"fs";import*as f from"path";import*as w from"os";function b(){let o=w.homedir(),e=f.join(o,".learn");return f.join(e,"credentials.json")}function L(){let o=w.homedir(),e=f.join(o,".learn");c.existsSync(e)||c.mkdirSync(e,{recursive:!0,mode:448})}function x(){try{let o=b();if(!c.existsSync(o))return null;let e=c.readFileSync(o,"utf-8"),t=JSON.parse(e);return!t.access_token||!t.refresh_token||!t.expires_at||!t.user_id?null:t}catch{return null}}function k(o){L();let e=b(),t=JSON.stringify(o,null,2);c.writeFileSync(e,t,{mode:384})}function v(){try{let o=b();c.existsSync(o)&&c.unlinkSync(o)}catch{}}function T(){let o=x();return o?new Date(o.expires_at)>new Date:!1}var S=class{constructor(e){this.appId=e.appId,this.baseUrl=e.baseUrl||"https://ctklearn.carsontkempf.workers.dev",this.timeout=e.timeout||3e4}async openBrowser(e){let{exec:t}=await import("child_process"),{promisify:r}=await import("util"),s=r(t),a=process.platform;try{a==="darwin"?await s(`open "${e}"`):a==="win32"?await s(`start "${e}"`):await s(`xdg-open "${e}"`)}catch(n){throw console.error("Failed to open browser:",n),new i("Failed to open browser",500)}}sleep(e){return new Promise(t=>setTimeout(t,e))}async login(){try{let e=await fetch(`${this.baseUrl}/api/auth/cli/device`,{method:"POST",headers:{"Content-Type":"application/json"}});if(!e.ok)throw new i("Failed to generate device code",e.status);let t=await e.json();console.log(`
2
- To authorize this application, visit:`),console.log(` ${t.verification_uri}`),console.log(`
3
- And enter the code:`),console.log(` ${t.user_code}`),console.log(`
1
+ import{a as U,b as q,c as N,d as F}from"./chunk-AS6G7JYX.mjs";var i=class extends Error{constructor(t,r,n){super(t);this.status=r;this.response=n;this.name="SecretsSDKError"}},y=class extends i{constructor(e="Origin not allowed for this token"){super(e,403),this.name="OriginMismatchError"}},w=class extends i{constructor(e="Rate limit exceeded",t=60,r=0,n=100){super(e,429),this.name="RateLimitError",this.retryAfter=t,this.remaining=r,this.limit=n}},S=class extends i{constructor(e="Invalid or expired token"){super(e,401),this.name="InvalidTokenError"}};var _=class{constructor(e={}){this.rateLimitInfo=null;this.appId=e.appId||null,this.token=e.token||e.sessionToken||null,this.baseUrl=e.baseUrl||"https://ctklearn.carsontkempf.workers.dev",this.timeout=e.timeout||3e4,this.retryOn429=e.retryOn429!==void 0?e.retryOn429:!0,this.zeroConfigMode=!this.appId&&!this.token}getUsage(){return this.rateLimitInfo}parseRateLimitHeaders(e){let t=e.get("X-RateLimit-Limit"),r=e.get("X-RateLimit-Remaining"),n=e.get("X-RateLimit-Reset");t&&r&&n&&(this.rateLimitInfo={limit:parseInt(t),remaining:parseInt(r),reset:parseInt(n)})}sleep(e){return new Promise(t=>setTimeout(t,e))}async fetchWithTimeout(e,t,r){let n=new AbortController,a=setTimeout(()=>n.abort(),r);try{return await fetch(e,{...t,signal:n.signal})}finally{clearTimeout(a)}}async call(e,t,r={}){let n=0,a=this.retryOn429?3:0;for(;;)try{let o=this.zeroConfigMode?`${this.baseUrl}/api/sdk/proxy`:`${this.baseUrl}/api/sdk/${this.appId}/proxy`,m={"Content-Type":"application/json"};!this.zeroConfigMode&&this.token&&(m.Authorization=`Bearer ${this.token}`);let l=await this.fetchWithTimeout(o,{method:"POST",headers:m,body:JSON.stringify({keyName:e,endpoint:t,method:r.method||"GET",body:r.body,headers:r.headers})},this.timeout);this.parseRateLimitHeaders(l.headers);let h=await l.json();if(!l.ok){let u=h.data?.message||h?.message||`Request failed with status ${l.status}`;if(l.status===403&&u.includes("Origin"))throw new y(u);if(l.status===401)throw new S(u);if(l.status===429){let M=this.rateLimitInfo?this.rateLimitInfo.reset-Math.floor(Date.now()/1e3):60,k=new w(u,M,this.rateLimitInfo?.remaining||0,this.rateLimitInfo?.limit||100);if(this.retryOn429&&n<a){n++;let T=Math.min(1e3*Math.pow(2,n-1),8e3);await this.sleep(T);continue}throw k}throw new i(u,l.status,h)}return h.data}catch(o){throw o instanceof i?o:o instanceof Error&&o.name==="AbortError"?new i("Request timeout",408):new i(o instanceof Error?o.message:"Unknown error occurred",500)}}async get(e,t,r){return this.call(e,t,{method:"GET",headers:r})}async post(e,t,r,n){return this.call(e,t,{method:"POST",body:r,headers:n})}async put(e,t,r,n){return this.call(e,t,{method:"PUT",body:r,headers:n})}async delete(e,t,r){return this.call(e,t,{method:"DELETE",headers:r})}async patch(e,t,r,n){return this.call(e,t,{method:"PATCH",body:r,headers:n})}setAppId(e){this.appId=e}async secretsRequest(e,t){if(!this.appId)throw new i("App ID required for secrets management. Call setAppId() first.",400);let r=await this.fetchWithTimeout(`${this.baseUrl}/api/projects/${this.appId}/secrets/manage?action=${e}`,{method:"POST",headers:{"Content-Type":"application/json"},body:t?JSON.stringify(t):void 0},this.timeout);if(!r.ok){let n=await r.json().catch(()=>({}));throw new i(n.message||`Secrets management failed: ${r.statusText}`,r.status,n)}return r.json()}async listSecrets(){return this.secretsRequest("list")}async createSecret(e){return this.secretsRequest("create",e)}async updateSecret(e){return this.secretsRequest("update",e)}async deleteSecret(e){return this.secretsRequest("delete",{name:e})}async syncEnv(e,t){return this.secretsRequest("sync",{secrets:e,provider:t})}};import*as c from"fs";import*as v from"path";import*as D from"os";function R(){let s=D.homedir(),e=v.join(s,".learn");return v.join(e,"credentials.json")}function z(){let s=D.homedir(),e=v.join(s,".learn");c.existsSync(e)||c.mkdirSync(e,{recursive:!0,mode:448})}function C(){try{let s=R();if(!c.existsSync(s))return null;let e=c.readFileSync(s,"utf-8"),t=JSON.parse(e);return t.access_token&&!t["learn-secrets-sdk"]&&!t["learn-auth-sdk"]?{"learn-secrets-sdk":t}:t}catch{return null}}function P(s){z();let e=R(),t=JSON.stringify(s,null,2);c.writeFileSync(e,t,{mode:384})}function b(s){let e=C();return e&&e[s]||null}function O(s,e){let t=C()||{};t[s]=e,P(t)}function J(s){let e=C();e&&(delete e[s],Object.keys(e).length===0?A():P(e))}function L(){return b("learn-secrets-sdk")}function A(){try{let s=R();c.existsSync(s)&&c.unlinkSync(s)}catch{}}function H(s){let e=b(s);return e!==null&&!!e.access_token}function K(){let s=L();return s?s.expires_at?new Date(s.expires_at)>new Date:!0:!1}import{exec as W}from"child_process";import{promisify as B}from"util";import{createHash as j}from"crypto";import*as p from"os";var x=B(W);async function E(){let s=process.platform;try{let e;if(s==="darwin")e=await G();else if(s==="linux")e=await V();else if(s==="win32")e=await X();else throw new Error(`Unsupported platform: ${s}`);let t=j("sha256").update(e).digest("hex").substring(0,32),r=JSON.stringify({machineId:t,cpus:p.cpus().length,arch:p.arch(),platform:p.platform(),homedir:p.homedir()}),n=j("sha256").update(r).digest("hex");return{machineId:t,fingerprint:n}}catch(e){throw new Error(`Failed to get machine ID: ${e.message}`)}}async function G(){try{let{stdout:s}=await x(`ioreg -l | grep IOPlatformSerialNumber | awk '{print $4}' | sed 's/"//g'`),{stdout:e}=await x("hostname"),t=s.trim(),r=e.trim();if(t&&r)return`${t}-${r}`;let{stdout:n}=await x(`ioreg -rd1 -c IOPlatformExpertDevice | grep IOPlatformUUID | awk '{print $3}' | sed 's/"//g'`);return n.trim()}catch{throw new Error("Failed to get macOS machine ID")}}async function V(){try{let{stdout:s}=await x("cat /etc/machine-id 2>/dev/null || cat /var/lib/dbus/machine-id 2>/dev/null"),e=s.trim();if(e)return e;throw new Error("Machine ID file not found")}catch{throw new Error("Failed to get Linux machine ID")}}async function X(){try{let{stdout:s}=await x("wmic csproduct get uuid"),t=s.split(`
2
+ `).map(r=>r.trim()).find(r=>r&&r!=="UUID");if(t)return t;throw new Error("Failed to parse machine UUID from WMIC")}catch{throw new Error("Failed to get Windows machine ID")}}var $=class{constructor(e){this.appId=e.appId,this.baseUrl=e.baseUrl||"https://ctklearn.carsontkempf.workers.dev",this.timeout=e.timeout||3e4}async openBrowser(e){let{exec:t}=await import("child_process"),{promisify:r}=await import("util"),n=r(t),a=process.platform;try{a==="darwin"?await n(`open "${e}"`):a==="win32"?await n(`start "${e}"`):await n(`xdg-open "${e}"`)}catch(o){throw console.error("Failed to open browser:",o),new i("Failed to open browser",500)}}sleep(e){return new Promise(t=>setTimeout(t,e))}async login(){let e="learn-secrets-sdk";try{console.log(`Authenticating with Learn (${e})...
3
+ `);let t=await E(),r=b(e);if(r&&r.access_token){let d=await fetch(`${this.baseUrl}/api/auth/cli/check-authorization`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({machine_id:t.machineId,sdk_name:e})});if(d.ok){let g=await d.json();if(g.authorized)return console.log("This machine is already authorized."),console.log(`Logged in as: ${g.user_email||g.user_id}`),{success:!0,user:g.user_email||g.user_id}}}let n=await fetch(`${this.baseUrl}/api/auth/cli/device`,{method:"POST",headers:{"Content-Type":"application/json"}});if(!n.ok)throw new i("Failed to generate device code",n.status);let a=await n.json();console.log(`
4
+ To authorize this device:`),console.log(`1. Visit: ${a.verification_uri}`),console.log(`2. Enter code: ${a.user_code}`),console.log("3. Complete 2FA verification"),console.log(`
4
5
  Opening browser...
5
- `),await this.openBrowser(t.verification_uri_complete);let r=t.interval*1e3,s=Math.ceil(t.expires_in/t.interval),a=0;for(;a<s;){await this.sleep(r),a++;let n=await fetch(`${this.baseUrl}/api/auth/cli/token`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({device_code:t.device_code})});if(n.status===202){process.stdout.write(".");continue}if(!n.ok){let d=await n.json().catch(()=>({}));throw new i(d.message||"Failed to exchange device code",n.status)}let p=await n.json(),u=new Date(Date.now()+p.expires_in*1e3);return k({access_token:p.access_token,refresh_token:p.refresh_token,expires_at:u.toISOString(),user_id:p.user_id}),console.log(`
6
+ `),await this.openBrowser(a.verification_uri_complete);let o=!1,m=a.interval*1e3,l=Math.ceil(a.expires_in/a.interval),h=0;for(;h<l;){await this.sleep(m),h++;let d=await fetch(`${this.baseUrl}/api/auth/cli/token`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({device_code:a.device_code})});if(d.status===202){process.stdout.write(".");continue}if(d.ok){o=!0;break}}if(!o)throw new i("Device authorization timeout",408);console.log(`
6
7
 
7
- Authentication successful!`),{success:!0,user:p.user_id}}throw new i("Authorization timeout",408)}catch(e){throw e instanceof i?e:new i(e.message||"Login failed",e.status||500)}}async isAuthenticated(){return T()}logout(){v(),console.log("Logged out successfully")}getAccessToken(){let e=x();if(!e)throw new i("Not authenticated. Run login() first.",401);if(new Date(e.expires_at)<=new Date)throw new i("Token expired. Run login() again.",401);return e.access_token}async request(e,t,r){let s=this.getAccessToken(),a={method:e,headers:{"Content-Type":"application/json",Authorization:`Bearer ${s}`}};r&&(a.body=JSON.stringify(r));let n=await fetch(`${this.baseUrl}${t}`,a);if(!n.ok){let p=await n.json().catch(()=>({}));throw new i(p.message||`Request failed: ${n.statusText}`,n.status,p)}return n.json()}async list(){return(await this.request("GET",`/api/projects/${this.appId}/secrets`)).secrets}async sync(e,t){return await this.request("POST",`/api/projects/${this.appId}/secrets/sync`,{secrets:e,options:{deleteMissing:t?.deleteMissing??!1,dryRun:t?.dryRun??!1}})}async importSecrets(e){return this.request("POST",`/api/projects/${this.appId}/import-secrets`,{version:e.version||"1.0",origins:e.origins,secrets:e.secrets})}async importFromFile(e){let{loadSecretsConfig:t}=await import("./env-resolver-Y4SFGOKB.mjs"),r=t(e);return this.importSecrets(r)}};export{h as InvalidTokenError,m as OriginMismatchError,g as RateLimitError,S as SecretsManagement,y as SecretsSDK,i as SecretsSDKError,D as loadSecretsConfig,C as resolveEnvInSecret,O as resolveEnvInSecrets,P as resolveEnvString};
8
+ Device authorized! Sending 2FA code...`);let u=await fetch(`${this.baseUrl}/api/auth/2fa/send`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({device_code:a.device_code,sdk_name:e,machine_id:t.machineId})});if(!u.ok){let d=await u.json().catch(()=>({}));throw new i(d.message||"Failed to send 2FA code",u.status)}console.log(`2FA code sent to your email.
9
+ `);let k=(await import("readline")).createInterface({input:process.stdin,output:process.stdout}),T=await new Promise(d=>{k.question("Enter 2FA code from email: ",g=>{k.close(),d(g)})}),I=await fetch(`${this.baseUrl}/api/auth/2fa/verify`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({device_code:a.device_code,code:T.trim()})});if(!I.ok){let d=await I.json().catch(()=>({}));throw new i(d.message||"2FA verification failed",I.status)}let f=await I.json();return O(e,{access_token:f.access_token,refresh_token:null,expires_at:null,user_id:f.user_id,machine_id:t.machineId,sdk_name:e,authorized_at:new Date().toISOString()}),console.log(`
10
+ Authentication successful!`),console.log(`Logged in as: ${f.user_email||f.user_id}`),console.log(`This machine is now permanently authorized.
11
+ `),{success:!0,user:f.user_email||f.user_id}}catch(t){throw t instanceof i?t:new i(t.message||"Login failed",t.status||500)}}async isAuthenticated(){return K()}logout(){A(),console.log("Logged out successfully")}getAccessToken(){let e=L();if(!e)throw new i("Not authenticated. Run login() first.",401);if(e.expires_at&&new Date(e.expires_at)<=new Date)throw new i("Token expired. Run login() again.",401);return e.access_token}async request(e,t,r){let n=this.getAccessToken(),a={method:e,headers:{"Content-Type":"application/json",Authorization:`Bearer ${n}`}};r&&(a.body=JSON.stringify(r));let o=await fetch(`${this.baseUrl}${t}`,a);if(!o.ok){let m=await o.json().catch(()=>({}));throw new i(m.message||`Request failed: ${o.statusText}`,o.status,m)}return o.json()}async list(){return(await this.request("GET",`/api/projects/${this.appId}/secrets`)).secrets}async sync(e,t){return await this.request("POST",`/api/projects/${this.appId}/secrets/sync`,{secrets:e,options:{deleteMissing:t?.deleteMissing??!1,dryRun:t?.dryRun??!1}})}async importSecrets(e){return this.request("POST",`/api/projects/${this.appId}/import-secrets`,{version:e.version||"1.0",origins:e.origins,secrets:e.secrets})}async importFromFile(e){let{loadSecretsConfig:t}=await import("./env-resolver-Y4SFGOKB.mjs"),r=t(e);return this.importSecrets(r)}};export{S as InvalidTokenError,y as OriginMismatchError,w as RateLimitError,$ as SecretsManagement,_ as SecretsSDK,i as SecretsSDKError,J as deleteSDKCredentials,E as getMachineId,H as hasSDKCredentials,F as loadSecretsConfig,C as readAllCredentials,b as readSDKCredentials,q as resolveEnvInSecret,N as resolveEnvInSecrets,U as resolveEnvString,P as writeAllCredentials,O as writeSDKCredentials};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "learn-secrets-sdk",
3
- "version": "1.5.0",
3
+ "version": "1.6.1",
4
4
  "description": "Secure API proxy SDK for static sites with domain-scoped tokens",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.esm.js",
@@ -15,7 +15,7 @@
15
15
  ],
16
16
  "repository": {
17
17
  "type": "git",
18
- "url": "https://github.com/carsontkempf/learn.git",
18
+ "url": "git+https://github.com/carsontkempf/learn.git",
19
19
  "directory": "packages/secrets-sdk"
20
20
  },
21
21
  "homepage": "https://github.com/carsontkempf/learn/tree/main/packages/secrets-sdk",