learn-secrets-sdk 1.10.0 → 1.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -294,11 +294,104 @@ custom providers supported - configure base url in dashboard.
294
294
 
295
295
  ## security
296
296
 
297
- * **api keys never exposed** - stored and injected server-side only
298
- * **origin-based auth** - browser origin header enforced, can't be faked
299
- * **no credentials in code** - zero-config means no tokens to leak
297
+ ### security model
298
+
299
+ the sdk uses origin-based authentication specifically designed for static sites. here's how it protects your api keys:
300
+
301
+ **zero credentials in code:**
302
+ * no api keys, tokens, or appid in your javascript
303
+ * anyone can view your source code - nothing sensitive to steal
304
+ * works because authentication happens via browser origin header
305
+
306
+ **browser origin header:**
307
+ * automatically sent by browser on every request
308
+ * contains the domain making the request (e.g., `https://yourdomain.com`)
309
+ * **cannot be spoofed** - browsers prevent javascript from modifying it
310
+ * enforced by browser security model (same-origin policy)
311
+
312
+ **server-side validation:**
313
+ ```
314
+ 1. browser sends request with Origin: https://yourdomain.com
315
+ 2. proxy checks if yourdomain.com is in allowed origins list
316
+ 3. if match: proxy injects api key server-side and calls external api
317
+ 4. if no match: request rejected with 403 error
318
+ 5. api keys never sent to browser
319
+ ```
320
+
321
+ **what if someone steals your code?**
322
+ * they deploy it on their domain (e.g., `https://attacker.com`)
323
+ * browser sends `Origin: https://attacker.com`
324
+ * proxy rejects - their domain not in your origins list
325
+ * api calls fail - they can't use your api keys
326
+
327
+ **comparison to alternatives:**
328
+
329
+ traditional approach (insecure):
330
+ ```javascript
331
+ // api key exposed in code
332
+ const apiKey = 'sk-proj-abc123...';
333
+ fetch('https://api.openai.com/v1/chat/completions', {
334
+ headers: { 'Authorization': `Bearer ${apiKey}` }
335
+ });
336
+ ```
337
+ problem: anyone viewing source code gets your api key
338
+
339
+ environment variables (doesn't work for static sites):
340
+ ```javascript
341
+ // process.env doesn't exist in browser
342
+ const apiKey = process.env.OPENAI_API_KEY; // undefined
343
+ ```
344
+ problem: static sites have no server to hold env vars
345
+
346
+ backend proxy (requires server):
347
+ ```javascript
348
+ // requires deploying and maintaining your own backend
349
+ fetch('https://yourbackend.com/api/openai', { ... });
350
+ ```
351
+ problem: need to deploy and maintain server infrastructure
352
+
353
+ learn-secrets approach (secure for static sites):
354
+ ```javascript
355
+ // zero credentials - authentication via origin header
356
+ const sdk = new SecretsSDK();
357
+ await sdk.post('openai', '/v1/chat/completions', { ... });
358
+ ```
359
+ solution: browser origin header provides authentication
360
+
361
+ ### security guarantees
362
+
363
+ * **api keys never exposed** - stored server-side, injected in proxy layer
364
+ * **origin header enforced** - browsers prevent spoofing, validated server-side
365
+ * **no credentials to leak** - zero-config means no tokens in your code
300
366
  * **rate limiting** - 100 req/min per domain prevents abuse
301
- * **instant updates** - change origins in dashboard anytime
367
+ * **instant revocation** - remove origins in dashboard to block access immediately
368
+ * **encrypted storage** - api keys encrypted at rest in database
369
+ * **https only** - sdk enforces https in production
370
+
371
+ ### best practices
372
+
373
+ **allowed origins:**
374
+ * only add domains you control
375
+ * use specific domains instead of wildcards
376
+ * localhost allowed for development only
377
+ * remove test domains before going live
378
+
379
+ **deployment:**
380
+ * deploy to production domain first
381
+ * then add that domain to allowed origins
382
+ * test that api calls work from production
383
+ * never commit credentials to version control
384
+
385
+ **monitoring:**
386
+ * check usage dashboard regularly
387
+ * watch for unexpected rate limit hits
388
+ * review origins list periodically
389
+ * remove unused origins
390
+
391
+ **incident response:**
392
+ * if domain compromised: remove from origins list immediately
393
+ * if api key leaked: regenerate in provider dashboard
394
+ * if suspecting abuse: check usage logs in dashboard
302
395
 
303
396
  ## documentation
304
397
 
@@ -0,0 +1 @@
1
+ "use strict";var SecretsSDK=(()=>{var h=Object.defineProperty;var T=Object.getOwnPropertyDescriptor;var R=Object.getOwnPropertyNames;var I=Object.prototype.hasOwnProperty;var k=(i,e)=>{for(var t in e)h(i,t,{get:e[t],enumerable:!0})},_=(i,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of R(e))!I.call(i,s)&&s!==t&&h(i,s,{get:()=>e[s],enumerable:!(r=T(e,s))||r.enumerable});return i};var S=i=>_(h({},"__esModule",{value:!0}),i);var P={};k(P,{InvalidTokenError:()=>g,OriginMismatchError:()=>c,RateLimitError:()=>u,SecretsSDK:()=>d,SecretsSDKError:()=>n});var n=class extends Error{constructor(t,r,s){super(t);this.status=r;this.response=s;this.name="SecretsSDKError"}},c=class extends n{constructor(e="Origin not allowed for this token"){super(e,403),this.name="OriginMismatchError"}},u=class extends n{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}},g=class extends n{constructor(e="Invalid or expired token"){super(e,401),this.name="InvalidTokenError"}};var d=class{constructor(e={}){this.rateLimitInfo=null;this.appId=e.appId||null,this.token=e.token||e.sessionToken||null,this.baseUrl=e.baseUrl||"https://cloudprototype.org",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,l=setTimeout(()=>s.abort(),r);try{return await fetch(e,{...t,signal:s.signal})}finally{clearTimeout(l)}}async call(e,t,r={}){let s=0,l=this.retryOn429?3:0;for(;;)try{let a=this.zeroConfigMode?`${this.baseUrl}/api/sdk/proxy`:`${this.baseUrl}/api/sdk/${this.appId}/proxy`,f={"Content-Type":"application/json"};!this.zeroConfigMode&&this.token&&(f.Authorization=`Bearer ${this.token}`);let o=await this.fetchWithTimeout(a,{method:"POST",headers:f,body:JSON.stringify({keyName:e,endpoint:t,method:r.method||"GET",body:r.body,headers:r.headers})},this.timeout);this.parseRateLimitHeaders(o.headers);let p=await o.json();if(!o.ok){let m=p.data?.message||p?.message||`Request failed with status ${o.status}`;if(o.status===403&&m.includes("Origin"))throw new c(m);if(o.status===401)throw new g(m);if(o.status===429){let y=this.rateLimitInfo?this.rateLimitInfo.reset-Math.floor(Date.now()/1e3):60,b=new u(m,y,this.rateLimitInfo?.remaining||0,this.rateLimitInfo?.limit||100);if(this.retryOn429&&s<l){s++;let x=Math.min(1e3*Math.pow(2,s-1),8e3);await this.sleep(x);continue}throw b}throw new n(m,o.status,p)}return p.data}catch(a){throw a instanceof n?a:a instanceof Error&&a.name==="AbortError"?new n("Request timeout",408):new n(a instanceof Error?a.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 n("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 n(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})}};return S(P);})();
@@ -0,0 +1,70 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
8
+ // src/env-resolver.ts
9
+ function resolveEnvString(value) {
10
+ return value.replace(/\$\{([A-Z0-9_]+)\}|\$([A-Z0-9_]+)/g, (match, var1, var2) => {
11
+ const varName = var1 || var2;
12
+ const envValue = process.env[varName];
13
+ if (envValue === void 0) {
14
+ throw new Error(`Environment variable ${varName} is not defined`);
15
+ }
16
+ return envValue;
17
+ });
18
+ }
19
+ function resolveEnvInSecret(secret) {
20
+ const resolved = {
21
+ name: resolveEnvString(secret.name),
22
+ provider: resolveEnvString(secret.provider),
23
+ api_key: resolveEnvString(secret.api_key)
24
+ };
25
+ if (secret.base_url) {
26
+ resolved.base_url = resolveEnvString(secret.base_url);
27
+ }
28
+ if (secret.auth_header) {
29
+ resolved.auth_header = resolveEnvString(secret.auth_header);
30
+ }
31
+ if (secret.auth_prefix) {
32
+ resolved.auth_prefix = resolveEnvString(secret.auth_prefix);
33
+ }
34
+ return resolved;
35
+ }
36
+ function resolveEnvInSecrets(secrets) {
37
+ return secrets.map(resolveEnvInSecret);
38
+ }
39
+ function loadSecretsConfig(configPath) {
40
+ const fs = __require("fs");
41
+ const path = __require("path");
42
+ const fullPath = path.resolve(configPath);
43
+ if (!fs.existsSync(fullPath)) {
44
+ throw new Error(`Config file not found: ${fullPath}`);
45
+ }
46
+ const configData = fs.readFileSync(fullPath, "utf-8");
47
+ const config = JSON.parse(configData);
48
+ if (!config.version) {
49
+ throw new Error('Config file missing "version" field');
50
+ }
51
+ if (!config.project) {
52
+ throw new Error('Config file missing "project" field');
53
+ }
54
+ if (!Array.isArray(config.secrets)) {
55
+ throw new Error('Config file missing "secrets" array');
56
+ }
57
+ const resolvedSecrets = resolveEnvInSecrets(config.secrets);
58
+ return {
59
+ version: config.version,
60
+ project: config.project,
61
+ secrets: resolvedSecrets
62
+ };
63
+ }
64
+
65
+ export {
66
+ resolveEnvString,
67
+ resolveEnvInSecret,
68
+ resolveEnvInSecrets,
69
+ loadSecretsConfig
70
+ };
@@ -362,7 +362,7 @@ Log in on ${this.baseUrl}`);
362
362
  /**
363
363
  * Make authenticated request
364
364
  */
365
- async request(method, path3, body) {
365
+ async request(method, path2, body) {
366
366
  const token = this.getAccessToken();
367
367
  const options = {
368
368
  method,
@@ -374,7 +374,7 @@ Log in on ${this.baseUrl}`);
374
374
  if (body) {
375
375
  options.body = JSON.stringify(body);
376
376
  }
377
- const response = await fetch(`${this.baseUrl}${path3}`, options);
377
+ const response = await fetch(`${this.baseUrl}${path2}`, options);
378
378
  if (!response.ok) {
379
379
  const errorData = await response.json().catch(() => ({}));
380
380
  throw new SecretsSDKError(
@@ -675,107 +675,6 @@ function summarizeDetectedSecrets(secrets) {
675
675
  return lines.join("\n");
676
676
  }
677
677
 
678
- // src/cli/commands/connect-cloudflare.ts
679
- function getApiUrl(baseUrl) {
680
- return baseUrl || "https://cloudprototype.org";
681
- }
682
- async function connectCloudflare(apiToken, baseUrl) {
683
- if (!apiToken.match(/^[A-Za-z0-9_-]{40,}$/)) {
684
- return {
685
- success: false,
686
- error: "Invalid API token format. Token should be 40+ alphanumeric characters"
687
- };
688
- }
689
- const validation = await validateCloudflareToken(apiToken);
690
- if (!validation.valid) {
691
- return {
692
- success: false,
693
- error: validation.error || "Token validation failed"
694
- };
695
- }
696
- try {
697
- const apiUrl = getApiUrl(baseUrl);
698
- const response = await fetch(`${apiUrl}/api/cloudflare/connect`, {
699
- method: "POST",
700
- headers: {
701
- "Content-Type": "application/json"
702
- },
703
- credentials: "include",
704
- body: JSON.stringify({
705
- apiToken,
706
- accountId: validation.accountId,
707
- accountName: validation.accountName
708
- })
709
- });
710
- if (!response.ok) {
711
- const error = await response.json();
712
- return {
713
- success: false,
714
- error: error.message || "Failed to store token"
715
- };
716
- }
717
- return {
718
- success: true,
719
- accountId: validation.accountId,
720
- accountName: validation.accountName
721
- };
722
- } catch (error) {
723
- return {
724
- success: false,
725
- error: error.message || "Failed to connect Cloudflare account"
726
- };
727
- }
728
- }
729
- async function validateCloudflareToken(token) {
730
- try {
731
- const response = await fetch("https://api.cloudflare.com/client/v4/user", {
732
- headers: {
733
- Authorization: `Bearer ${token}`,
734
- "Content-Type": "application/json"
735
- }
736
- });
737
- if (!response.ok) {
738
- const error = await response.json();
739
- return {
740
- valid: false,
741
- error: error.errors?.[0]?.message || "Invalid token"
742
- };
743
- }
744
- const data = await response.json();
745
- const accountsResponse = await fetch("https://api.cloudflare.com/client/v4/accounts", {
746
- headers: {
747
- Authorization: `Bearer ${token}`,
748
- "Content-Type": "application/json"
749
- }
750
- });
751
- if (!accountsResponse.ok) {
752
- return {
753
- valid: false,
754
- error: "Token does not have access to accounts"
755
- };
756
- }
757
- const accountsData = await accountsResponse.json();
758
- const accounts = accountsData.result || [];
759
- if (accounts.length === 0) {
760
- return {
761
- valid: false,
762
- error: "No accounts found for this token"
763
- };
764
- }
765
- const account = accounts[0];
766
- return {
767
- valid: true,
768
- accountId: account.id,
769
- accountName: account.name
770
- };
771
- } catch (error) {
772
- return {
773
- valid: false,
774
- error: error.message || "Failed to validate token"
775
- };
776
- }
777
- }
778
-
779
678
  // src/cli/commands/init.ts
780
679
  async function initCommand(options) {
781
680
  if (!hasValidCredentials()) {
@@ -865,67 +764,16 @@ Onboarding site for project: ${projectId}`);
865
764
  console.log(` - ${failure.name}: ${failure.error}`);
866
765
  }
867
766
  }
767
+ const dashboardUrl = options.baseUrl || "https://cloudprototype.org";
868
768
  console.log("\nYour site is ready!");
869
769
  console.log("\nAdd to your static site code:");
870
770
  console.log(" const sdk = new SecretsSDK();");
871
771
  console.log(" // Uses origin-based auth - no token needed!");
872
- console.log("\n--- Cloudflare Worker Setup ---");
873
- const dashboardUrl = options.baseUrl || "https://cloudprototype.org";
874
- let cloudflareConnected = false;
875
- try {
876
- const statusResponse = await fetch(`${dashboardUrl}/api/cloudflare/status`, {
877
- credentials: "include"
878
- });
879
- cloudflareConnected = statusResponse.ok;
880
- } catch {
881
- cloudflareConnected = false;
882
- }
883
- if (cloudflareConnected) {
884
- console.log("Cloudflare account is already connected.");
885
- } else {
886
- console.log("To deploy secrets via Cloudflare Worker, we need your Cloudflare API token.");
887
- console.log("\nRequired permissions:");
888
- console.log(" - Workers Scripts: Edit");
889
- console.log(" - Workers Routes: Edit");
890
- console.log(" - Account Settings: Read");
891
- console.log("\nCreate token at: https://dash.cloudflare.com/profile/api-tokens");
892
- const readline = await import("readline");
893
- const rl = readline.createInterface({
894
- input: process.stdin,
895
- output: process.stdout
896
- });
897
- const apiToken = await new Promise((resolve2) => {
898
- rl.question("\nCloudflare API Token (or press Enter to skip): ", (answer) => {
899
- rl.close();
900
- resolve2(answer.trim());
901
- });
902
- });
903
- if (apiToken) {
904
- console.log("\nValidating token...");
905
- const result2 = await connectCloudflare(apiToken, options.baseUrl);
906
- if (result2.success) {
907
- console.log("Cloudflare account connected");
908
- console.log(` Account: ${result2.accountName}`);
909
- cloudflareConnected = true;
910
- } else {
911
- console.log(`
912
- Warning: Failed to connect Cloudflare: ${result2.error}`);
913
- console.log("You can connect later by running: learn-secrets deploy");
914
- }
915
- } else {
916
- console.log("\nSkipped Cloudflare connection.");
917
- console.log("You can connect later when you run: learn-secrets deploy");
918
- }
919
- }
920
772
  console.log(`
921
773
  Next steps:`);
922
- console.log(` 1. Run: learn-secrets deploy`);
923
- if (cloudflareConnected) {
924
- console.log(` (Deploy Worker to Cloudflare with your secrets)`);
925
- } else {
926
- console.log(` (Connect Cloudflare and deploy Worker with your secrets)`);
927
- }
928
- console.log(` 2. Visit: ${dashboardUrl}/dashboard to manage secrets`);
774
+ console.log(` 1. Run: learn-secrets deploy (to update secrets)`);
775
+ console.log(` 2. Visit: ${dashboardUrl}/dashboard/secrets to manage secrets`);
776
+ console.log(` 3. View usage at: ${dashboardUrl}/dashboard/settings/usage`);
929
777
  const config = {
930
778
  project: projectId,
931
779
  origins,
@@ -994,87 +842,6 @@ Configuration saved to: ${configFile}`);
994
842
 
995
843
  // src/cli/commands/deploy.ts
996
844
  import fs5 from "fs";
997
-
998
- // src/cli/commands/provision-worker.ts
999
- import path2 from "path";
1000
- import { fileURLToPath } from "url";
1001
- var __filename = fileURLToPath(import.meta.url);
1002
- var __dirname = path2.dirname(__filename);
1003
- function getApiUrl2(baseUrl) {
1004
- return baseUrl || "https://cloudprototype.org";
1005
- }
1006
- async function provisionWorker(workerName, origins, envVars, baseUrl) {
1007
- const publicVars = [];
1008
- const secretVars = [];
1009
- for (const { key, value } of envVars) {
1010
- if (isPublicConfig(key)) {
1011
- publicVars.push({ key, value });
1012
- } else {
1013
- secretVars.push({ key, value });
1014
- }
1015
- }
1016
- try {
1017
- const apiUrl = getApiUrl2(baseUrl);
1018
- const deployResponse = await fetch(`${apiUrl}/api/cloudflare/provision-worker`, {
1019
- method: "POST",
1020
- headers: {
1021
- "Content-Type": "application/json"
1022
- },
1023
- credentials: "include",
1024
- body: JSON.stringify({
1025
- workerName,
1026
- allowedOrigins: origins,
1027
- publicVars: publicVars.map((v) => ({
1028
- key: `PUBLIC_${v.key}`,
1029
- value: v.value
1030
- })),
1031
- secrets: secretVars.map((v) => ({
1032
- key: `SECRET_${v.key}`,
1033
- value: v.value
1034
- }))
1035
- })
1036
- });
1037
- if (!deployResponse.ok) {
1038
- const error = await deployResponse.json();
1039
- return {
1040
- success: false,
1041
- error: error.message || "Failed to deploy Worker"
1042
- };
1043
- }
1044
- const result = await deployResponse.json();
1045
- return {
1046
- success: true,
1047
- workerUrl: result.workerUrl,
1048
- workerName: result.workerName
1049
- };
1050
- } catch (error) {
1051
- return {
1052
- success: false,
1053
- error: error.message || "Failed to provision Worker"
1054
- };
1055
- }
1056
- }
1057
- function isPublicConfig(key) {
1058
- const publicPatterns = [
1059
- /_DOMAIN$/,
1060
- /_CLIENT_ID$/,
1061
- /_AUDIENCE$/,
1062
- /_CALLBACK_URL$/,
1063
- /_ISSUER$/,
1064
- /_TEAM_ID$/,
1065
- /_KEY_ID$/,
1066
- /^API_BASE_URL$/,
1067
- /^PUBLIC_/
1068
- ];
1069
- for (const pattern of publicPatterns) {
1070
- if (pattern.test(key)) {
1071
- return true;
1072
- }
1073
- }
1074
- return false;
1075
- }
1076
-
1077
- // src/cli/commands/deploy.ts
1078
845
  async function deployCommand(options = {}) {
1079
846
  if (!hasValidCredentials()) {
1080
847
  console.error('Error: Not authenticated. Run "learn-secrets login" first.');
@@ -1148,101 +915,18 @@ Deploying secrets to project: ${config.project}`);
1148
915
  console.log("\nSuccess!");
1149
916
  console.log(` Created: ${result.results.created} secrets`);
1150
917
  console.log(` Updated: ${result.results.updated} secrets`);
918
+ console.log(` Failed: ${result.results.failed} secrets`);
1151
919
  if (result.results.failed > 0) {
1152
- console.log(` Failed: ${result.results.failed} secrets`);
920
+ console.log("\nFailed uploads:");
1153
921
  for (const failure of result.results.details.failed) {
1154
- console.log(` - ${failure.name}: ${failure.error}`);
922
+ console.log(` - ${failure.name}: ${failure.error}`);
1155
923
  }
1156
924
  }
1157
925
  const dashboardUrl = options.baseUrl || "https://cloudprototype.org";
926
+ console.log("\nSecrets deployed successfully!");
1158
927
  console.log(`
1159
- Secrets deployed! View them at: ${dashboardUrl}/dashboard/collections/secrets`);
1160
- console.log("\n--- Cloudflare Worker Deployment ---");
1161
- let cloudflareConnected = false;
1162
- try {
1163
- const statusResponse = await fetch(`${dashboardUrl}/api/cloudflare/status`, {
1164
- credentials: "include"
1165
- });
1166
- cloudflareConnected = statusResponse.ok;
1167
- } catch {
1168
- cloudflareConnected = false;
1169
- }
1170
- if (!cloudflareConnected) {
1171
- console.log("Cloudflare account not connected.");
1172
- console.log("\nTo deploy Worker, we need your Cloudflare API token.");
1173
- console.log("Required permissions:");
1174
- console.log(" - Workers Scripts: Edit");
1175
- console.log(" - Workers Routes: Edit");
1176
- console.log(" - Account Settings: Read");
1177
- console.log("\nCreate token at: https://dash.cloudflare.com/profile/api-tokens");
1178
- const readline = await import("readline");
1179
- const rl = readline.createInterface({
1180
- input: process.stdin,
1181
- output: process.stdout
1182
- });
1183
- const apiToken = await new Promise((resolve2) => {
1184
- rl.question("\nCloudflare API Token (or press Enter to skip): ", (answer) => {
1185
- rl.close();
1186
- resolve2(answer.trim());
1187
- });
1188
- });
1189
- if (apiToken) {
1190
- console.log("\nValidating token...");
1191
- const result2 = await connectCloudflare(apiToken, options.baseUrl);
1192
- if (result2.success) {
1193
- console.log("Cloudflare account connected");
1194
- console.log(` Account: ${result2.accountName}`);
1195
- cloudflareConnected = true;
1196
- } else {
1197
- console.log(`
1198
- Error: Failed to connect Cloudflare: ${result2.error}`);
1199
- console.log("Worker deployment skipped.");
1200
- }
1201
- } else {
1202
- console.log("\nSkipped Cloudflare connection. Worker deployment skipped.");
1203
- }
1204
- } else {
1205
- console.log("Cloudflare account is connected.");
1206
- }
1207
- if (cloudflareConnected) {
1208
- const readline = await import("readline");
1209
- const rl = readline.createInterface({
1210
- input: process.stdin,
1211
- output: process.stdout
1212
- });
1213
- const shouldProvision = await new Promise((resolve2) => {
1214
- rl.question("\nDeploy/update Worker to Cloudflare? (y/N): ", (answer) => {
1215
- rl.close();
1216
- resolve2(answer.trim());
1217
- });
1218
- });
1219
- if (shouldProvision.toLowerCase() === "y" || shouldProvision.toLowerCase() === "yes") {
1220
- console.log("\nDeploying Worker to Cloudflare...");
1221
- const workerName = `learn-secrets-${config.project.toLowerCase()}`;
1222
- const provisionResult = await provisionWorker(
1223
- workerName,
1224
- config.origins,
1225
- envVars,
1226
- options.baseUrl
1227
- );
1228
- if (provisionResult.success) {
1229
- console.log("\nWorker deployed successfully");
1230
- console.log(` Name: ${provisionResult.workerName}`);
1231
- console.log(` URL: ${provisionResult.workerUrl}`);
1232
- config.workerUrl = provisionResult.workerUrl;
1233
- fs5.writeFileSync(configFile, JSON.stringify(config, null, 2));
1234
- console.log("\nNext steps:");
1235
- console.log(` 1. Test Worker: curl ${provisionResult.workerUrl}/health`);
1236
- console.log(` 2. Update your frontend to use Worker URL`);
1237
- console.log(` 3. Test origin validation from your site`);
1238
- } else {
1239
- console.log(`
1240
- Error: Worker deployment failed: ${provisionResult.error}`);
1241
- }
1242
- } else {
1243
- console.log("\nSkipped Worker deployment.");
1244
- }
1245
- }
928
+ View your secrets in the dashboard:`);
929
+ console.log(` ${dashboardUrl}/dashboard/collections/secrets`);
1246
930
  } catch (error) {
1247
931
  console.error("\nDeploy failed:", error.message);
1248
932
  if (error.status === 401) {
@@ -1257,7 +941,7 @@ Error: Worker deployment failed: ${provisionResult.error}`);
1257
941
 
1258
942
  // src/cli/index.ts
1259
943
  var program = new Command();
1260
- program.name("learn-secrets").description("CLI tool for managing API secrets with Cloudflare Worker deployment").version("1.10.0");
944
+ program.name("learn-secrets").description("CLI tool for managing secrets with the Learn platform").version("1.11.1");
1261
945
  program.command("login").description("Authenticate with Learn Secrets via browser").option("--base-url <url>", "Custom base URL for API").action(async (options) => {
1262
946
  await loginCommand(options);
1263
947
  });
@@ -0,0 +1,12 @@
1
+ import {
2
+ loadSecretsConfig,
3
+ resolveEnvInSecret,
4
+ resolveEnvInSecrets,
5
+ resolveEnvString
6
+ } from "./chunk-Y4RHJLDQ.mjs";
7
+ export {
8
+ loadSecretsConfig,
9
+ resolveEnvInSecret,
10
+ resolveEnvInSecrets,
11
+ resolveEnvString
12
+ };