learn-secrets-sdk 1.6.4 → 1.6.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,6 +4,9 @@ import "./chunk-Y6FXYEAI.mjs";
4
4
  // src/cli/index.ts
5
5
  import { Command } from "commander";
6
6
 
7
+ // src/cli/commands/login.ts
8
+ import fs2 from "fs";
9
+
7
10
  // src/types.ts
8
11
  var SecretsSDKError = class extends Error {
9
12
  constructor(message, status, response) {
@@ -270,7 +273,7 @@ Log in on ${this.baseUrl}`);
270
273
  });
271
274
  console.log("Opening browser...\n");
272
275
  await this.openBrowser(deviceData.verification_uri_complete);
273
- let deviceAuthorized = false;
276
+ let tokenData = null;
274
277
  const interval = deviceData.interval * 1e3;
275
278
  const maxAttempts = Math.ceil(deviceData.expires_in / deviceData.interval);
276
279
  let attempts = 0;
@@ -291,30 +294,14 @@ Log in on ${this.baseUrl}`);
291
294
  continue;
292
295
  }
293
296
  if (tokenResponse.ok) {
294
- deviceAuthorized = true;
297
+ tokenData = await tokenResponse.json();
295
298
  break;
296
299
  }
297
300
  }
298
- if (!deviceAuthorized) {
301
+ if (!tokenData) {
299
302
  throw new SecretsSDKError("Device authorization timeout", 408);
300
303
  }
301
304
  console.log("\n\nDevice authorized! Retrieving access token...");
302
- const finalTokenResponse = await fetch(`${this.baseUrl}/api/auth/cli/token`, {
303
- method: "POST",
304
- headers: {
305
- "Content-Type": "application/json"
306
- },
307
- body: JSON.stringify({
308
- device_code: deviceData.device_code
309
- })
310
- });
311
- if (!finalTokenResponse.ok) {
312
- throw new SecretsSDKError(
313
- "Failed to retrieve access token",
314
- finalTokenResponse.status
315
- );
316
- }
317
- const tokenData = await finalTokenResponse.json();
318
305
  writeSDKCredentials(SDK_NAME, {
319
306
  access_token: tokenData.access_token,
320
307
  refresh_token: null,
@@ -457,17 +444,76 @@ Log in on ${this.baseUrl}`);
457
444
  // src/cli/commands/login.ts
458
445
  async function loginCommand(options = {}) {
459
446
  console.log("Authenticating with Learn Secrets...\n");
447
+ const baseUrl = options.baseUrl || "https://cloudprototype.org";
460
448
  const mgmt = new SecretsManagement({
461
449
  appId: "temp",
462
450
  // Will be set after login
463
- baseUrl: options.baseUrl
451
+ baseUrl
464
452
  });
465
453
  try {
466
454
  const result = await mgmt.login();
467
455
  console.log(`
468
456
  Logged in as: ${result.user}`);
469
457
  console.log("Credentials saved to ~/.learn/credentials.json");
470
- console.log("\nYou can now run: learn-secrets init --origins yourdomain.com");
458
+ console.log("\nFetching your projects...");
459
+ const creds = readCredentials();
460
+ if (!creds) {
461
+ throw new Error("Failed to read credentials");
462
+ }
463
+ const projectsResponse = await fetch(`${baseUrl}/api/projects`, {
464
+ headers: {
465
+ Authorization: `Bearer ${creds.access_token}`
466
+ }
467
+ });
468
+ if (!projectsResponse.ok) {
469
+ throw new Error("Failed to fetch projects");
470
+ }
471
+ const { projects } = await projectsResponse.json();
472
+ if (projects.length === 0) {
473
+ console.log("\nNo projects found.");
474
+ console.log("Create a project at: https://cloudprototype.org/dashboard");
475
+ console.log("\nAfter creating a project, run: learn-secrets login");
476
+ return;
477
+ }
478
+ let selectedProject;
479
+ if (projects.length === 1) {
480
+ selectedProject = projects[0];
481
+ console.log(`
482
+ Auto-selected project: ${selectedProject.name} (${selectedProject.appid})`);
483
+ } else {
484
+ console.log("\nYou have multiple projects:");
485
+ projects.forEach((p, idx) => {
486
+ console.log(` ${idx + 1}. ${p.name} (${p.appid})`);
487
+ });
488
+ const readline = await import("readline");
489
+ const rl = readline.createInterface({
490
+ input: process.stdin,
491
+ output: process.stdout
492
+ });
493
+ const answer = await new Promise((resolve2) => {
494
+ rl.question("\nSelect a project (1-" + projects.length + "): ", resolve2);
495
+ });
496
+ rl.close();
497
+ const selection = parseInt(answer.trim());
498
+ if (isNaN(selection) || selection < 1 || selection > projects.length) {
499
+ console.error("Invalid selection");
500
+ process.exit(1);
501
+ }
502
+ selectedProject = projects[selection - 1];
503
+ console.log(`
504
+ Selected: ${selectedProject.name}`);
505
+ }
506
+ const configFile = "secrets-config.json";
507
+ const config = {
508
+ project: selectedProject.appid,
509
+ origins: []
510
+ };
511
+ fs2.writeFileSync(configFile, JSON.stringify(config, null, 2));
512
+ console.log(`
513
+ Project configuration saved to: ${configFile}`);
514
+ console.log("\nNext steps:");
515
+ console.log(" 1. Run: learn-secrets init --origins yourdomain.com --env .netlify_env");
516
+ console.log(" 2. This will upload your secrets and generate an SDK token");
471
517
  } catch (error) {
472
518
  console.error("\nLogin failed:", error.message);
473
519
  process.exit(1);
@@ -475,7 +521,7 @@ Logged in as: ${result.user}`);
475
521
  }
476
522
 
477
523
  // src/cli/commands/init.ts
478
- import fs2 from "fs";
524
+ import fs3 from "fs";
479
525
 
480
526
  // src/cli/utils/env-parser.ts
481
527
  import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
@@ -643,8 +689,20 @@ async function initCommand(options) {
643
689
  console.error("Example: learn-secrets init --origins mysite.com,localhost");
644
690
  process.exit(1);
645
691
  }
646
- if (!options.project) {
647
- console.error("Error: --project is required");
692
+ let projectId = options.project;
693
+ const configFile = "secrets-config.json";
694
+ if (!projectId && fs3.existsSync(configFile)) {
695
+ try {
696
+ const config = JSON.parse(fs3.readFileSync(configFile, "utf8"));
697
+ projectId = config.project;
698
+ if (projectId) {
699
+ console.log(`Using project from config: ${projectId}`);
700
+ }
701
+ } catch (err) {
702
+ }
703
+ }
704
+ if (!projectId) {
705
+ console.error('Error: --project is required (or run "learn-secrets login" first)');
648
706
  console.error("Example: learn-secrets init --project my-app-id --origins mysite.com");
649
707
  process.exit(1);
650
708
  }
@@ -654,7 +712,7 @@ async function initCommand(options) {
654
712
  process.exit(1);
655
713
  }
656
714
  console.log(`
657
- Onboarding site for project: ${options.project}`);
715
+ Onboarding site for project: ${projectId}`);
658
716
  console.log(`Origins: ${origins.join(", ")}
659
717
  `);
660
718
  const envFile = options.env || ".env";
@@ -693,7 +751,7 @@ Onboarding site for project: ${options.project}`);
693
751
  }
694
752
  console.log("\nUploading secrets...");
695
753
  const mgmt = new SecretsManagement({
696
- appId: options.project,
754
+ appId: projectId,
697
755
  baseUrl: options.baseUrl
698
756
  });
699
757
  try {
@@ -722,14 +780,13 @@ Onboarding site for project: ${options.project}`);
722
780
  }
723
781
  console.log("\nYour site is ready!");
724
782
  console.log(`Visit: https://ctklearn.carsontkempf.workers.dev to manage secrets`);
725
- const configFile = "secrets-config.json";
726
783
  const config = {
727
- project: options.project,
784
+ project: projectId,
728
785
  origins
729
786
  };
730
- fs2.writeFileSync(configFile, JSON.stringify(config, null, 2));
787
+ fs3.writeFileSync(configFile, JSON.stringify(config, null, 2));
731
788
  console.log(`
732
- Created local config: ${configFile}`);
789
+ Updated local config: ${configFile}`);
733
790
  } catch (error) {
734
791
  console.error("\nUpload failed:", error.message);
735
792
  if (error.status === 401) {
@@ -937,8 +994,8 @@ LEARN_ORIGIN=${options.origin}
937
994
  # Token prefix: ${sdkToken.slice(-4)}
938
995
  `;
939
996
  try {
940
- const fs5 = await import("fs");
941
- fs5.writeFileSync(configPath, configContent, "utf-8");
997
+ const fs6 = await import("fs");
998
+ fs6.writeFileSync(configPath, configContent, "utf-8");
942
999
  console.log(`
943
1000
  \u{1F4BE} Configuration saved to: ${configPath}`);
944
1001
  } catch (error) {
@@ -949,30 +1006,30 @@ LEARN_ORIGIN=${options.origin}
949
1006
  }
950
1007
 
951
1008
  // src/cli/commands/config.ts
952
- import fs3 from "fs";
1009
+ import fs4 from "fs";
953
1010
  async function configCommand(options) {
954
1011
  const configFile = "secrets-config.json";
955
- if (!fs3.existsSync(configFile)) {
1012
+ if (!fs4.existsSync(configFile)) {
956
1013
  console.error("Run learn-secrets init first to generate config.");
957
1014
  return;
958
1015
  }
959
- const config = JSON.parse(fs3.readFileSync(configFile, "utf8"));
1016
+ const config = JSON.parse(fs4.readFileSync(configFile, "utf8"));
960
1017
  if (options.origins) {
961
1018
  config.origins = options.origins.split(",").map((o) => o.trim());
962
1019
  console.log("Updated origins:", config.origins);
963
1020
  }
964
- fs3.writeFileSync(configFile, JSON.stringify(config, null, 2));
1021
+ fs4.writeFileSync(configFile, JSON.stringify(config, null, 2));
965
1022
  }
966
1023
 
967
1024
  // src/cli/commands/deploy.ts
968
- import fs4 from "fs";
1025
+ import fs5 from "fs";
969
1026
  async function deployCommand() {
970
1027
  const configFile = "secrets-config.json";
971
- if (!fs4.existsSync(configFile)) {
1028
+ if (!fs5.existsSync(configFile)) {
972
1029
  console.error("No config found.");
973
1030
  return;
974
1031
  }
975
- const config = JSON.parse(fs4.readFileSync(configFile, "utf8"));
1032
+ const config = JSON.parse(fs5.readFileSync(configFile, "utf8"));
976
1033
  console.log("Deploying secrets config to backend:", config);
977
1034
  }
978
1035
 
@@ -985,7 +1042,7 @@ program.command("setup").description("Complete setup: create project, generate t
985
1042
  program.command("login").description("Authenticate with Learn Secrets via browser").option("--base-url <url>", "Custom base URL for API").action(async (options) => {
986
1043
  await loginCommand(options);
987
1044
  });
988
- program.command("init").description("Initialize secrets for a new site").requiredOption("-o, --origins <domains>", "Comma-separated list of allowed origins (e.g., mysite.com,localhost)").requiredOption("-p, --project <appid>", "Project app ID from dashboard").option("-e, --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) => {
1045
+ program.command("init").description("Initialize secrets for a new site").requiredOption("-o, --origins <domains>", "Comma-separated list of allowed origins (e.g., mysite.com,localhost)").option("-p, --project <appid>", "Project app ID (auto-detected from login if not specified)").option("-e, --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) => {
989
1046
  await initCommand(options);
990
1047
  });
991
1048
  program.command("config").description("Configure allowed origins").option("--origins <origins>", "Comma separated origins").action(async (options) => {
@@ -1,10 +1,10 @@
1
- "use strict";var SecretsSDK=(()=>{var Q=Object.create;var D=Object.defineProperty;var Y=Object.getOwnPropertyDescriptor;var ee=Object.getOwnPropertyNames;var te=Object.getPrototypeOf,re=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 se=(t,e)=>()=>(t&&(e=t(t=0)),e);var J=(t,e)=>{for(var r in e)D(t,r,{get:e[r],enumerable:!0})},H=(t,e,r,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of ee(e))!re.call(t,n)&&n!==r&&D(t,n,{get:()=>e[n],enumerable:!(s=Y(e,n))||s.enumerable});return t};var y=(t,e,r)=>(r=t!=null?Q(te(t)):{},H(e||!t||!t.__esModule?D(r,"default",{value:t,enumerable:!0}):r,t)),ne=t=>H(D({},"__esModule",{value:!0}),t);var Z={};J(Z,{loadSecretsConfig:()=>z,resolveEnvInSecret:()=>$,resolveEnvInSecrets:()=>A,resolveEnvString:()=>u});function u(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 $(t){let e={name:u(t.name),provider:u(t.provider),api_key:u(t.api_key)};return t.base_url&&(e.base_url=u(t.base_url)),t.auth_header&&(e.auth_header=u(t.auth_header)),t.auth_prefix&&(e.auth_prefix=u(t.auth_prefix)),e}function A(t){return t.map($)}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=A(i.secrets);return{version:i.version,project:i.project,secrets:a}}var F=se(()=>{"use strict"});var de={};J(de,{InvalidTokenError:()=>b,OriginMismatchError:()=>S,RateLimitError:()=>x,SecretsManagement:()=>j,SecretsSDK:()=>R,SecretsSDKError:()=>o,deleteSDKCredentials:()=>W,getMachineId:()=>L,hasSDKCredentials:()=>B,loadSecretsConfig:()=>z,readAllCredentials:()=>I,readSDKCredentials:()=>v,resolveEnvInSecret:()=>$,resolveEnvInSecrets:()=>A,resolveEnvString:()=>u,writeAllCredentials:()=>O,writeSDKCredentials:()=>E});var o=class extends Error{constructor(r,s,n){super(r);this.status=s;this.response=n;this.name="SecretsSDKError"}},S=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`,p={"Content-Type":"application/json"};!this.zeroConfigMode&&this.token&&(p.Authorization=`Bearer ${this.token}`);let l=await this.fetchWithTimeout(a,{method:"POST",headers:p,body:JSON.stringify({keyName:e,endpoint:r,method:s.method||"GET",body:s.body,headers:s.headers})},this.timeout);this.parseRateLimitHeaders(l.headers);let w=await l.json();if(!l.ok){let h=w.data?.message||w?.message||`Request failed with status ${l.status}`;if(l.status===403&&h.includes("Origin"))throw new S(h);if(l.status===401)throw new b(h);if(l.status===429){let T=this.rateLimitInfo?this.rateLimitInfo.reset-Math.floor(Date.now()/1e3):60,k=new x(h,T,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 k}throw new o(h,l.status,w)}return w.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=y(c("fs")),P=y(c("path")),M=y(c("os"));function K(){let t=M.homedir(),e=P.join(t,".learn");return P.join(e,"credentials.json")}function ie(){let t=M.homedir(),e=P.join(t,".learn");d.existsSync(e)||d.mkdirSync(e,{recursive:!0,mode:448})}function I(){try{let t=K();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){ie();let e=K(),r=JSON.stringify(t,null,2);d.writeFileSync(e,r,{mode:384})}function v(t){let e=I();return e&&e[t]||null}function E(t,e){let r=I()||{};r[t]=e,O(r)}function W(t){let e=I();e&&(delete e[t],Object.keys(e).length===0?q():O(e))}function U(){return v("learn-secrets-sdk")}function q(){try{let t=K();d.existsSync(t)&&d.unlinkSync(t)}catch{}}function B(t){let e=v(t);return e!==null&&!!e.access_token}function G(){let t=U();return t?t.expires_at?new Date(t.expires_at)>new Date:!0:!1}var V=c("child_process"),X=c("util"),N=c("crypto"),g=y(c("os")),_=(0,X.promisify)(V.exec);async function L(){let t=process.platform;try{let e;if(t==="darwin")e=await oe();else if(t==="linux")e=await ae();else if(t==="win32")e=await ce();else throw new Error(`Unsupported platform: ${t}`);let r=(0,N.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,N.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 oe(){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 ae(){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 ce(){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://cloudprototype.org",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 L(),s=v(e);if(s&&s.access_token){let f=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(f.ok){let C=await f.json();if(C.authorized)return console.log("This machine is already authorized."),console.log(`Logged in as: ${C.user_email||C.user_id}`),{success:!0,user:C.user_email||C.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(`
1
+ "use strict";var SecretsSDK=(()=>{var X=Object.create;var _=Object.defineProperty;var Z=Object.getOwnPropertyDescriptor;var Q=Object.getOwnPropertyNames;var Y=Object.getPrototypeOf,ee=Object.prototype.hasOwnProperty;var d=(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 te=(t,e)=>()=>(t&&(e=t(t=0)),e);var z=(t,e)=>{for(var r in e)_(t,r,{get:e[r],enumerable:!0})},F=(t,e,r,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of Q(e))!ee.call(t,n)&&n!==r&&_(t,n,{get:()=>e[n],enumerable:!(s=Z(e,n))||s.enumerable});return t};var y=(t,e,r)=>(r=t!=null?X(Y(t)):{},F(e||!t||!t.__esModule?_(r,"default",{value:t,enumerable:!0}):r,t)),re=t=>F(_({},"__esModule",{value:!0}),t);var V={};z(V,{loadSecretsConfig:()=>q,resolveEnvInSecret:()=>E,resolveEnvInSecrets:()=>L,resolveEnvString:()=>p});function p(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 E(t){let e={name:p(t.name),provider:p(t.provider),api_key:p(t.api_key)};return t.base_url&&(e.base_url=p(t.base_url)),t.auth_header&&(e.auth_header=p(t.auth_header)),t.auth_prefix&&(e.auth_prefix=p(t.auth_prefix)),e}function L(t){return t.map(E)}function q(t){let e=d("fs"),s=d("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=L(i.secrets);return{version:i.version,project:i.project,secrets:a}}var N=te(()=>{"use strict"});var ae={};z(ae,{InvalidTokenError:()=>b,OriginMismatchError:()=>S,RateLimitError:()=>x,SecretsManagement:()=>$,SecretsSDK:()=>T,SecretsSDKError:()=>o,deleteSDKCredentials:()=>J,getMachineId:()=>O,hasSDKCredentials:()=>H,loadSecretsConfig:()=>q,readAllCredentials:()=>k,readSDKCredentials:()=>v,resolveEnvInSecret:()=>E,resolveEnvInSecrets:()=>L,resolveEnvString:()=>p,writeAllCredentials:()=>R,writeSDKCredentials:()=>P});var o=class extends Error{constructor(r,s,n){super(r);this.status=s;this.response=n;this.name="SecretsSDKError"}},S=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 T=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`,m={"Content-Type":"application/json"};!this.zeroConfigMode&&this.token&&(m.Authorization=`Bearer ${this.token}`);let c=await this.fetchWithTimeout(a,{method:"POST",headers:m,body:JSON.stringify({keyName:e,endpoint:r,method:s.method||"GET",body:s.body,headers:s.headers})},this.timeout);this.parseRateLimitHeaders(c.headers);let w=await c.json();if(!c.ok){let h=w.data?.message||w?.message||`Request failed with status ${c.status}`;if(c.status===403&&h.includes("Origin"))throw new S(h);if(c.status===401)throw new b(h);if(c.status===429){let C=this.rateLimitInfo?this.rateLimitInfo.reset-Math.floor(Date.now()/1e3):60,u=new x(h,C,this.rateLimitInfo?.remaining||0,this.rateLimitInfo?.limit||100);if(this.retryOn429&&n<i){n++;let f=Math.min(1e3*Math.pow(2,n-1),8e3);await this.sleep(f);continue}throw u}throw new o(h,c.status,w)}return w.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 l=y(d("fs")),D=y(d("path")),A=y(d("os"));function M(){let t=A.homedir(),e=D.join(t,".learn");return D.join(e,"credentials.json")}function se(){let t=A.homedir(),e=D.join(t,".learn");l.existsSync(e)||l.mkdirSync(e,{recursive:!0,mode:448})}function k(){try{let t=M();if(!l.existsSync(t))return null;let e=l.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 R(t){se();let e=M(),r=JSON.stringify(t,null,2);l.writeFileSync(e,r,{mode:384})}function v(t){let e=k();return e&&e[t]||null}function P(t,e){let r=k()||{};r[t]=e,R(r)}function J(t){let e=k();e&&(delete e[t],Object.keys(e).length===0?K():R(e))}function j(){return v("learn-secrets-sdk")}function K(){try{let t=M();l.existsSync(t)&&l.unlinkSync(t)}catch{}}function H(t){let e=v(t);return e!==null&&!!e.access_token}function W(){let t=j();return t?t.expires_at?new Date(t.expires_at)>new Date:!0:!1}var B=d("child_process"),G=d("util"),U=d("crypto"),g=y(d("os")),I=(0,G.promisify)(B.exec);async function O(){let t=process.platform;try{let e;if(t==="darwin")e=await ne();else if(t==="linux")e=await ie();else if(t==="win32")e=await oe();else throw new Error(`Unsupported platform: ${t}`);let r=(0,U.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,U.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 ne(){try{let{stdout:t}=await I(`ioreg -l | grep IOPlatformSerialNumber | awk '{print $4}' | sed 's/"//g'`),{stdout:e}=await I("hostname"),r=t.trim(),s=e.trim();if(r&&s)return`${r}-${s}`;let{stdout:n}=await I(`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 ie(){try{let{stdout:t}=await I("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 oe(){try{let{stdout:t}=await I("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 $=class{constructor(e){this.appId=e.appId,this.baseUrl=e.baseUrl||"https://cloudprototype.org",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 O(),s=v(e);if(s&&s.access_token){let u=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(u.ok){let f=await u.json();if(f.authorized)return console.log("This machine is already authorized."),console.log(`Logged in as: ${f.user_email||f.user_id}`),{success:!0,user:f.user_email||f.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
4
  Log in on ${this.baseUrl}`),console.log("Login at:"),console.log(i.verification_uri_complete),console.log(`
5
- Press ENTER to open in the browser...`);let p=(await import("readline")).createInterface({input:process.stdin,output:process.stdout});await new Promise(f=>{p.question("",()=>{p.close(),f()})}),console.log(`Opening browser...
6
- `),await this.openBrowser(i.verification_uri_complete);let l=!1,w=i.interval*1e3,h=Math.ceil(i.expires_in/i.interval),T=0;for(;T<h;){await this.sleep(w),T++;let f=await fetch(`${this.baseUrl}/api/auth/cli/token`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({device_code:i.device_code})});if(f.status===202){process.stdout.write(".");continue}if(f.ok){l=!0;break}}if(!l)throw new o("Device authorization timeout",408);console.log(`
5
+ Press ENTER to open in the browser...`);let m=(await import("readline")).createInterface({input:process.stdin,output:process.stdout});await new Promise(u=>{m.question("",()=>{m.close(),u()})}),console.log(`Opening browser...
6
+ `),await this.openBrowser(i.verification_uri_complete);let c=null,w=i.interval*1e3,h=Math.ceil(i.expires_in/i.interval),C=0;for(;C<h;){await this.sleep(w),C++;let u=await fetch(`${this.baseUrl}/api/auth/cli/token`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({device_code:i.device_code})});if(u.status===202){process.stdout.write(".");continue}if(u.ok){c=await u.json();break}}if(!c)throw new o("Device authorization timeout",408);return console.log(`
7
7
 
8
- Device authorized! Retrieving access token...`);let k=await fetch(`${this.baseUrl}/api/auth/cli/token`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({device_code:i.device_code})});if(!k.ok)throw new o("Failed to retrieve access token",k.status);let m=await k.json();return E(e,{access_token:m.access_token,refresh_token:null,expires_at:null,user_id:m.user_id,machine_id:r.machineId,sdk_name:e,authorized_at:new Date().toISOString()}),console.log(`
9
- Authentication successful!`),console.log(`Logged in as: ${m.user_email||m.user_id}`),console.log(`This machine is now permanently authorized.
10
- `),{success:!0,user:m.user_email||m.user_id}}catch(r){throw r instanceof o?r:new o(r.message||"Login failed",r.status||500)}}async isAuthenticated(){return G()}logout(){q(),console.log("Logged out successfully")}getAccessToken(){let e=U();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 p=await a.json().catch(()=>({}));throw new o(p.message||`Request failed: ${a.statusText}`,a.status,p)}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(()=>(F(),Z)),s=r(e);return this.importSecrets(s)}};F();return ne(de);})();
8
+ Device authorized! Retrieving access token...`),P(e,{access_token:c.access_token,refresh_token:null,expires_at:null,user_id:c.user_id,machine_id:r.machineId,sdk_name:e,authorized_at:new Date().toISOString()}),console.log(`
9
+ Authentication successful!`),console.log(`Logged in as: ${c.user_email||c.user_id}`),console.log(`This machine is now permanently authorized.
10
+ `),{success:!0,user:c.user_email||c.user_id}}catch(r){throw r instanceof o?r:new o(r.message||"Login failed",r.status||500)}}async isAuthenticated(){return W()}logout(){K(),console.log("Logged out successfully")}getAccessToken(){let e=j();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 m=await a.json().catch(()=>({}));throw new o(m.message||`Request failed: ${a.statusText}`,a.status,m)}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(()=>(N(),V)),s=r(e);return this.importSecrets(s)}};N();return re(ae);})();
package/dist/index.mjs CHANGED
@@ -1,10 +1,10 @@
1
- import{a as K,b as U,c as j,d as q}from"./chunk-AS6G7JYX.mjs";var i=class extends Error{constructor(t,r,n){super(t);this.status=r;this.response=n;this.name="SecretsSDKError"}},w=class extends i{constructor(e="Origin not allowed for this token"){super(e,403),this.name="OriginMismatchError"}},S=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}},b=class extends i{constructor(e="Invalid or expired token"){super(e,401),this.name="InvalidTokenError"}};var T=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`,l={"Content-Type":"application/json"};!this.zeroConfigMode&&this.token&&(l.Authorization=`Bearer ${this.token}`);let d=await this.fetchWithTimeout(o,{method:"POST",headers:l,body:JSON.stringify({keyName:e,endpoint:t,method:r.method||"GET",body:r.body,headers:r.headers})},this.timeout);this.parseRateLimitHeaders(d.headers);let h=await d.json();if(!d.ok){let m=h.data?.message||h?.message||`Request failed with status ${d.status}`;if(d.status===403&&m.includes("Origin"))throw new w(m);if(d.status===401)throw new b(m);if(d.status===429){let I=this.rateLimitInfo?this.rateLimitInfo.reset-Math.floor(Date.now()/1e3):60,f=new S(m,I,this.rateLimitInfo?.remaining||0,this.rateLimitInfo?.limit||100);if(this.retryOn429&&n<a){n++;let u=Math.min(1e3*Math.pow(2,n-1),8e3);await this.sleep(u);continue}throw f}throw new i(m,d.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 _ from"os";function D(){let s=_.homedir(),e=v.join(s,".learn");return v.join(e,"credentials.json")}function N(){let s=_.homedir(),e=v.join(s,".learn");c.existsSync(e)||c.mkdirSync(e,{recursive:!0,mode:448})}function C(){try{let s=D();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 R(s){N();let e=D(),t=JSON.stringify(s,null,2);c.writeFileSync(e,t,{mode:384})}function x(s){let e=C();return e&&e[s]||null}function P(s,e){let t=C()||{};t[s]=e,R(t)}function z(s){let e=C();e&&(delete e[s],Object.keys(e).length===0?L():R(e))}function O(){return x("learn-secrets-sdk")}function L(){try{let s=D();c.existsSync(s)&&c.unlinkSync(s)}catch{}}function F(s){let e=x(s);return e!==null&&!!e.access_token}function $(){let s=O();return s?s.expires_at?new Date(s.expires_at)>new Date:!0:!1}import{exec as J}from"child_process";import{promisify as H}from"util";import{createHash as A}from"crypto";import*as p from"os";var k=H(J);async function E(){let s=process.platform;try{let e;if(s==="darwin")e=await W();else if(s==="linux")e=await B();else if(s==="win32")e=await G();else throw new Error(`Unsupported platform: ${s}`);let t=A("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=A("sha256").update(r).digest("hex");return{machineId:t,fingerprint:n}}catch(e){throw new Error(`Failed to get machine ID: ${e.message}`)}}async function W(){try{let{stdout:s}=await k(`ioreg -l | grep IOPlatformSerialNumber | awk '{print $4}' | sed 's/"//g'`),{stdout:e}=await k("hostname"),t=s.trim(),r=e.trim();if(t&&r)return`${t}-${r}`;let{stdout:n}=await k(`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 B(){try{let{stdout:s}=await k("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 G(){try{let{stdout:s}=await k("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 M=class{constructor(e){this.appId=e.appId,this.baseUrl=e.baseUrl||"https://cloudprototype.org",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=x(e);if(r&&r.access_token){let g=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(g.ok){let y=await g.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 i("Failed to generate device code",n.status);let a=await n.json();console.log(`
4
- Log in on ${this.baseUrl}`),console.log("Login at:"),console.log(a.verification_uri_complete),console.log(`
5
- Press ENTER to open in the browser...`);let l=(await import("readline")).createInterface({input:process.stdin,output:process.stdout});await new Promise(g=>{l.question("",()=>{l.close(),g()})}),console.log(`Opening browser...
6
- `),await this.openBrowser(a.verification_uri_complete);let d=!1,h=a.interval*1e3,m=Math.ceil(a.expires_in/a.interval),I=0;for(;I<m;){await this.sleep(h),I++;let g=await fetch(`${this.baseUrl}/api/auth/cli/token`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({device_code:a.device_code})});if(g.status===202){process.stdout.write(".");continue}if(g.ok){d=!0;break}}if(!d)throw new i("Device authorization timeout",408);console.log(`
1
+ import{a as K,b as $,c as A,d as U}from"./chunk-AS6G7JYX.mjs";var i=class extends Error{constructor(t,r,n){super(t);this.status=r;this.response=n;this.name="SecretsSDKError"}},f=class extends i{constructor(e="Origin not allowed for this token"){super(e,403),this.name="OriginMismatchError"}},y=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}},w=class extends i{constructor(e="Invalid or expired token"){super(e,401),this.name="InvalidTokenError"}};var v=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,c=setTimeout(()=>n.abort(),r);try{return await fetch(e,{...t,signal:n.signal})}finally{clearTimeout(c)}}async call(e,t,r={}){let n=0,c=this.retryOn429?3:0;for(;;)try{let o=this.zeroConfigMode?`${this.baseUrl}/api/sdk/proxy`:`${this.baseUrl}/api/sdk/${this.appId}/proxy`,u={"Content-Type":"application/json"};!this.zeroConfigMode&&this.token&&(u.Authorization=`Bearer ${this.token}`);let a=await this.fetchWithTimeout(o,{method:"POST",headers:u,body:JSON.stringify({keyName:e,endpoint:t,method:r.method||"GET",body:r.body,headers:r.headers})},this.timeout);this.parseRateLimitHeaders(a.headers);let h=await a.json();if(!a.ok){let m=h.data?.message||h?.message||`Request failed with status ${a.status}`;if(a.status===403&&m.includes("Origin"))throw new f(m);if(a.status===401)throw new w(m);if(a.status===429){let x=this.rateLimitInfo?this.rateLimitInfo.reset-Math.floor(Date.now()/1e3):60,l=new y(m,x,this.rateLimitInfo?.remaining||0,this.rateLimitInfo?.limit||100);if(this.retryOn429&&n<c){n++;let g=Math.min(1e3*Math.pow(2,n-1),8e3);await this.sleep(g);continue}throw l}throw new i(m,a.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 d from"fs";import*as k from"path";import*as C from"os";function T(){let s=C.homedir(),e=k.join(s,".learn");return k.join(e,"credentials.json")}function j(){let s=C.homedir(),e=k.join(s,".learn");d.existsSync(e)||d.mkdirSync(e,{recursive:!0,mode:448})}function I(){try{let s=T();if(!d.existsSync(s))return null;let e=d.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 _(s){j();let e=T(),t=JSON.stringify(s,null,2);d.writeFileSync(e,t,{mode:384})}function S(s){let e=I();return e&&e[s]||null}function D(s,e){let t=I()||{};t[s]=e,_(t)}function q(s){let e=I();e&&(delete e[s],Object.keys(e).length===0?P():_(e))}function R(){return S("learn-secrets-sdk")}function P(){try{let s=T();d.existsSync(s)&&d.unlinkSync(s)}catch{}}function N(s){let e=S(s);return e!==null&&!!e.access_token}function E(){let s=R();return s?s.expires_at?new Date(s.expires_at)>new Date:!0:!1}import{exec as z}from"child_process";import{promisify as F}from"util";import{createHash as M}from"crypto";import*as p from"os";var b=F(z);async function O(){let s=process.platform;try{let e;if(s==="darwin")e=await J();else if(s==="linux")e=await H();else if(s==="win32")e=await W();else throw new Error(`Unsupported platform: ${s}`);let t=M("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=M("sha256").update(r).digest("hex");return{machineId:t,fingerprint:n}}catch(e){throw new Error(`Failed to get machine ID: ${e.message}`)}}async function J(){try{let{stdout:s}=await b(`ioreg -l | grep IOPlatformSerialNumber | awk '{print $4}' | sed 's/"//g'`),{stdout:e}=await b("hostname"),t=s.trim(),r=e.trim();if(t&&r)return`${t}-${r}`;let{stdout:n}=await b(`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 H(){try{let{stdout:s}=await b("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 W(){try{let{stdout:s}=await b("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 L=class{constructor(e){this.appId=e.appId,this.baseUrl=e.baseUrl||"https://cloudprototype.org",this.timeout=e.timeout||3e4}async openBrowser(e){let{exec:t}=await import("child_process"),{promisify:r}=await import("util"),n=r(t),c=process.platform;try{c==="darwin"?await n(`open "${e}"`):c==="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 O(),r=S(e);if(r&&r.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:t.machineId,sdk_name:e})});if(l.ok){let g=await l.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 c=await n.json();console.log(`
4
+ Log in on ${this.baseUrl}`),console.log("Login at:"),console.log(c.verification_uri_complete),console.log(`
5
+ Press ENTER to open in the browser...`);let u=(await import("readline")).createInterface({input:process.stdin,output:process.stdout});await new Promise(l=>{u.question("",()=>{u.close(),l()})}),console.log(`Opening browser...
6
+ `),await this.openBrowser(c.verification_uri_complete);let a=null,h=c.interval*1e3,m=Math.ceil(c.expires_in/c.interval),x=0;for(;x<m;){await this.sleep(h),x++;let l=await fetch(`${this.baseUrl}/api/auth/cli/token`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({device_code:c.device_code})});if(l.status===202){process.stdout.write(".");continue}if(l.ok){a=await l.json();break}}if(!a)throw new i("Device authorization timeout",408);return console.log(`
7
7
 
8
- Device authorized! Retrieving access token...`);let f=await fetch(`${this.baseUrl}/api/auth/cli/token`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({device_code:a.device_code})});if(!f.ok)throw new i("Failed to retrieve access token",f.status);let u=await f.json();return P(e,{access_token:u.access_token,refresh_token:null,expires_at:null,user_id:u.user_id,machine_id:t.machineId,sdk_name:e,authorized_at:new Date().toISOString()}),console.log(`
9
- Authentication successful!`),console.log(`Logged in as: ${u.user_email||u.user_id}`),console.log(`This machine is now permanently authorized.
10
- `),{success:!0,user:u.user_email||u.user_id}}catch(t){throw t instanceof i?t:new i(t.message||"Login failed",t.status||500)}}async isAuthenticated(){return $()}logout(){L(),console.log("Logged out successfully")}getAccessToken(){let e=O();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 l=await o.json().catch(()=>({}));throw new i(l.message||`Request failed: ${o.statusText}`,o.status,l)}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{b as InvalidTokenError,w as OriginMismatchError,S as RateLimitError,M as SecretsManagement,T as SecretsSDK,i as SecretsSDKError,z as deleteSDKCredentials,E as getMachineId,F as hasSDKCredentials,q as loadSecretsConfig,C as readAllCredentials,x as readSDKCredentials,U as resolveEnvInSecret,j as resolveEnvInSecrets,K as resolveEnvString,R as writeAllCredentials,P as writeSDKCredentials};
8
+ Device authorized! Retrieving access token...`),D(e,{access_token:a.access_token,refresh_token:null,expires_at:null,user_id:a.user_id,machine_id:t.machineId,sdk_name:e,authorized_at:new Date().toISOString()}),console.log(`
9
+ Authentication successful!`),console.log(`Logged in as: ${a.user_email||a.user_id}`),console.log(`This machine is now permanently authorized.
10
+ `),{success:!0,user:a.user_email||a.user_id}}catch(t){throw t instanceof i?t:new i(t.message||"Login failed",t.status||500)}}async isAuthenticated(){return E()}logout(){P(),console.log("Logged out successfully")}getAccessToken(){let e=R();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(),c={method:e,headers:{"Content-Type":"application/json",Authorization:`Bearer ${n}`}};r&&(c.body=JSON.stringify(r));let o=await fetch(`${this.baseUrl}${t}`,c);if(!o.ok){let u=await o.json().catch(()=>({}));throw new i(u.message||`Request failed: ${o.statusText}`,o.status,u)}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{w as InvalidTokenError,f as OriginMismatchError,y as RateLimitError,L as SecretsManagement,v as SecretsSDK,i as SecretsSDKError,q as deleteSDKCredentials,O as getMachineId,N as hasSDKCredentials,U as loadSecretsConfig,I as readAllCredentials,S as readSDKCredentials,$ as resolveEnvInSecret,A as resolveEnvInSecrets,K as resolveEnvString,_ as writeAllCredentials,D as writeSDKCredentials};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "learn-secrets-sdk",
3
- "version": "1.6.4",
3
+ "version": "1.6.6",
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",