opencloud-platform-sdk 2.0.0 → 3.0.0

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
@@ -4,11 +4,12 @@ Official SDK for **OpenCloud** - Monetize your AI apps with ease.
4
4
 
5
5
  **You control the AI, we handle the payments.**
6
6
 
7
- ## What's New in v2.0
7
+ ## What's New in v3.0
8
8
 
9
- - **Use your own AI** - OpenAI, Anthropic, your own model, whatever you want
10
- - **Set your own price** - Charge what you want per use
11
- - **85/15 split** - You keep 85%, platform takes 15%
9
+ - **Standalone Mode** - Works on any domain, no iframe required
10
+ - **Popup Authentication** - Secure OAuth-style login via popup
11
+ - **Automatic Login** - SDK prompts for login when charging
12
+ - **Cross-Domain Support** - Full CORS support for external apps
12
13
 
13
14
  ## Installation
14
15
 
@@ -21,30 +22,64 @@ npm install opencloud-platform-sdk
21
22
  ```typescript
22
23
  import { opencloud } from 'opencloud-platform-sdk';
23
24
 
24
- // 1. Initialize with your app ID
25
+ // 1. Initialize with your app ID (slug)
25
26
  opencloud.init({ appId: 'your-app-slug' });
26
27
 
27
- // 2. Check if user can afford
28
- const canUse = await opencloud.canAfford();
29
- if (!canUse) {
30
- opencloud.requestTopUp();
31
- return;
28
+ // 2. Charge the user (opens login popup if needed)
29
+ async function handleAIFeature() {
30
+ const result = await opencloud.charge('generate_image');
31
+
32
+ if (!result.success) {
33
+ if (result.error === 'CANCELLED') {
34
+ // User closed the login popup
35
+ return;
36
+ }
37
+ if (result.error === 'INSUFFICIENT_BALANCE') {
38
+ // SDK already opened top-up popup
39
+ return;
40
+ }
41
+ }
42
+
43
+ // Charge successful! Now run your AI logic
44
+ const image = await myAIService.generate(prompt);
45
+ return image;
32
46
  }
47
+ ```
33
48
 
34
- // 3. Call YOUR OWN AI (you control this)
35
- const response = await fetch('https://api.openai.com/v1/chat/completions', {
36
- method: 'POST',
37
- headers: { 'Authorization': 'Bearer YOUR_API_KEY' },
38
- body: JSON.stringify({
39
- model: 'gpt-4',
40
- messages: [{ role: 'user', content: 'Hello!' }]
41
- })
42
- });
49
+ ## How It Works
50
+
51
+ 1. User visits your app (e.g., `https://your-app.vercel.app`)
52
+ 2. User clicks "Generate Image" (or any paid feature)
53
+ 3. SDK checks if user has OpenCloud session
54
+ 4. If not logged in → Opens OpenCloud login popup
55
+ 5. User logs in Popup closes → SDK gets session token
56
+ 6. SDK charges user's OpenCloud wallet
57
+ 7. Your app continues with the AI feature
43
58
 
44
- // 4. Charge the user
45
- const result = await opencloud.trackUsage({ action: 'chat' });
46
- console.log('Charged:', result.charged);
47
- console.log('User balance:', result.userBalance);
59
+ ```
60
+ Your App OpenCloud
61
+ │ │
62
+ User clicks "Generate" │
63
+ │ ─────────────────────────────> │
64
+ │ │
65
+ │ SDK: opencloud.charge() │
66
+ │ ─────────────────────────────> │
67
+ │ │
68
+ │ No session? Open popup │
69
+ │ <───────────────────────────── │
70
+ │ │
71
+ │ User logs in via popup │
72
+ │ ─────────────────────────────> │
73
+ │ │
74
+ │ Session token returned │
75
+ │ <───────────────────────────── │
76
+ │ │
77
+ │ Charge wallet │
78
+ │ ─────────────────────────────> │
79
+ │ │
80
+ │ Success! Continue... │
81
+ │ <───────────────────────────── │
82
+ │ │
48
83
  ```
49
84
 
50
85
  ## API Reference
@@ -55,91 +90,121 @@ Initialize the SDK.
55
90
 
56
91
  ```typescript
57
92
  opencloud.init({
58
- appId: 'your-app-slug', // Required
59
- apiUrl: 'https://opencloud.app' // Optional
93
+ appId: 'your-app-slug', // Required - your app's slug
94
+ apiUrl: 'https://opencloud.app' // Optional - defaults to production
60
95
  });
61
96
  ```
62
97
 
63
- ### `opencloud.canAfford()`
98
+ ### `opencloud.charge(action?, metadata?)`
64
99
 
65
- Check if user has enough balance.
100
+ Charge the user. Opens login popup if not authenticated.
66
101
 
67
102
  ```typescript
68
- const canUse = await opencloud.canAfford();
69
- if (!canUse) {
70
- opencloud.requestTopUp();
71
- return;
103
+ const result = await opencloud.charge('chat_message', { model: 'gpt-4' });
104
+
105
+ // Result:
106
+ {
107
+ success: true,
108
+ charged: 0.10,
109
+ balance: 4.90,
110
+ transactionId: 'tx_abc123'
111
+ }
112
+
113
+ // Or on error:
114
+ {
115
+ success: false,
116
+ error: 'INSUFFICIENT_BALANCE' | 'CANCELLED' | 'UNKNOWN'
72
117
  }
73
118
  ```
74
119
 
75
- ### `opencloud.trackUsage(options)`
120
+ ### `opencloud.withCharge(fn, options)`
76
121
 
77
- Charge the user. This is the main method to monetize your app.
122
+ Execute a function and charge the user. Handles auth and balance checks automatically.
78
123
 
79
124
  ```typescript
80
- const result = await opencloud.trackUsage({
81
- action: 'chat_message', // Optional: for analytics
82
- metadata: { model: 'gpt-4' } // Optional: extra data
83
- });
125
+ const image = await opencloud.withCharge(async () => {
126
+ return await myAIService.generateImage(prompt);
127
+ }, { action: 'generate_image' });
128
+ ```
84
129
 
85
- // Response:
86
- {
87
- success: true,
88
- charged: 0.10,
89
- userBalance: 4.90,
90
- transactionId: 'tx_abc123'
130
+ ### `opencloud.isAuthenticated()`
131
+
132
+ Check if user is logged in.
133
+
134
+ ```typescript
135
+ if (opencloud.isAuthenticated()) {
136
+ console.log('User is logged in');
91
137
  }
92
138
  ```
93
139
 
94
- ### `opencloud.withUsage(fn, options)` (Optional)
140
+ ### `opencloud.login()`
95
141
 
96
- Convenience method that combines canAfford + your code + trackUsage.
142
+ Open login popup manually.
97
143
 
98
144
  ```typescript
99
- const response = await opencloud.withUsage(async () => {
100
- // Your AI call here - only runs if user can pay
101
- return await myAICall();
102
- }, { action: 'chat' });
145
+ const session = await opencloud.login();
146
+ if (session) {
147
+ console.log('Logged in as:', session.user.email);
148
+ }
149
+ ```
150
+
151
+ ### `opencloud.logout()`
152
+
153
+ Log out the current user.
154
+
155
+ ```typescript
156
+ opencloud.logout();
157
+ ```
158
+
159
+ ### `opencloud.getUser()`
160
+
161
+ Get current user info (synchronous).
162
+
163
+ ```typescript
164
+ const user = opencloud.getUser();
165
+ // { id, email, username, balance }
103
166
  ```
104
167
 
105
168
  ### `opencloud.getBalance()`
106
169
 
107
- Get user's current balance.
170
+ Get user's current balance (async, fetches from server).
108
171
 
109
172
  ```typescript
110
173
  const balance = await opencloud.getBalance();
111
174
  console.log(`Balance: $${balance}`);
112
175
  ```
113
176
 
114
- ### `opencloud.getAppPrice()`
177
+ ### `opencloud.canAfford()`
115
178
 
116
- Get your app's price per use.
179
+ Check if user can afford to use the app.
117
180
 
118
181
  ```typescript
119
- const price = await opencloud.getAppPrice();
120
- console.log(`Price: $${price}`);
182
+ const canUse = await opencloud.canAfford();
183
+ if (!canUse) {
184
+ opencloud.openTopUp();
185
+ }
121
186
  ```
122
187
 
123
- ### `opencloud.requestTopUp()`
188
+ ### `opencloud.openTopUp()`
124
189
 
125
- Open the payment dialog for user to add credits.
190
+ Open top-up popup for user to add credits.
126
191
 
127
192
  ```typescript
128
- opencloud.requestTopUp();
193
+ opencloud.openTopUp();
129
194
  ```
130
195
 
131
- ### `opencloud.getUserInfo()`
196
+ ### `opencloud.getAppPrice()`
132
197
 
133
- Get current user information.
198
+ Get your app's price per use.
134
199
 
135
200
  ```typescript
136
- const user = await opencloud.getUserInfo();
137
- // { id, email, balance, username }
201
+ const price = await opencloud.getAppPrice();
202
+ console.log(`Price: $${price}`);
138
203
  ```
139
204
 
140
205
  ### `opencloud.isPreview()`
141
206
 
142
- Check if running in development mode (localhost, etc).
207
+ Check if running in development mode.
143
208
 
144
209
  ```typescript
145
210
  if (opencloud.isPreview()) {
@@ -157,35 +222,44 @@ User pays: $0.10 per use
157
222
  └─ Platform: $0.015 (15%)
158
223
  ```
159
224
 
160
- ## Error Handling
161
-
162
- ```typescript
163
- try {
164
- await opencloud.trackUsage({ action: 'chat' });
165
- } catch (error) {
166
- if (error.message === 'INSUFFICIENT_BALANCE') {
167
- opencloud.requestTopUp();
168
- }
169
- }
170
- ```
171
-
172
225
  ## Preview Mode
173
226
 
174
- When running locally (localhost, etc), the SDK automatically enters preview mode:
175
- - `trackUsage()` doesn't charge real money
227
+ When running locally (localhost), the SDK automatically enters preview mode:
228
+ - `charge()` doesn't charge real money
176
229
  - `getBalance()` returns 999.99
177
230
  - `canAfford()` always returns true
178
231
 
179
232
  This lets you develop without worrying about charges.
180
233
 
234
+ ## Error Handling
235
+
236
+ ```typescript
237
+ const result = await opencloud.charge('chat');
238
+
239
+ if (!result.success) {
240
+ switch (result.error) {
241
+ case 'CANCELLED':
242
+ // User closed the login popup
243
+ showMessage('Please log in to use this feature');
244
+ break;
245
+ case 'INSUFFICIENT_BALANCE':
246
+ // SDK already opened top-up popup
247
+ showMessage('Please add credits to continue');
248
+ break;
249
+ default:
250
+ showMessage('Something went wrong');
251
+ }
252
+ }
253
+ ```
254
+
181
255
  ## Publishing Your App
182
256
 
183
- 1. Deploy your app to Vercel via [opencloud.app/deploy](/deploy)
257
+ 1. Deploy your app to Vercel
184
258
  2. Go to [opencloud.app/publish](/publish)
185
- 3. Select your deployed project
259
+ 3. Create a new app with your deployed URL
186
260
  4. Set name, description, and **price per use**
187
- 5. Sign the creator agreement (85/15 split)
188
- 6. Publish!
261
+ 5. Get your app slug
262
+ 6. Use the slug in `opencloud.init({ appId: 'your-slug' })`
189
263
 
190
264
  ## TypeScript Support
191
265
 
@@ -195,8 +269,8 @@ Full TypeScript definitions included:
195
269
  import {
196
270
  opencloud,
197
271
  OpenCloudSDK,
198
- TrackUsageParams,
199
- TrackUsageResponse,
272
+ ChargeResult,
273
+ UserSession,
200
274
  UserInfo
201
275
  } from 'opencloud-platform-sdk';
202
276
  ```
package/dist/cli.d.mts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/cli.js ADDED
@@ -0,0 +1,161 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // src/cli.ts
27
+ var fs = __toESM(require("fs"));
28
+ var path = __toESM(require("path"));
29
+ var VERCEL_HEADERS = {
30
+ headers: [
31
+ {
32
+ source: "/(.*)",
33
+ headers: [
34
+ { key: "X-Frame-Options", value: "ALLOWALL" },
35
+ { key: "Content-Security-Policy", value: "frame-ancestors *" }
36
+ ]
37
+ }
38
+ ]
39
+ };
40
+ function findProjectRoot() {
41
+ let dir = process.cwd();
42
+ while (dir !== path.dirname(dir)) {
43
+ if (fs.existsSync(path.join(dir, "package.json"))) {
44
+ return dir;
45
+ }
46
+ dir = path.dirname(dir);
47
+ }
48
+ return process.cwd();
49
+ }
50
+ function setupVercelJson(projectRoot) {
51
+ const vercelJsonPath = path.join(projectRoot, "vercel.json");
52
+ try {
53
+ let config = {};
54
+ if (fs.existsSync(vercelJsonPath)) {
55
+ const content = fs.readFileSync(vercelJsonPath, "utf-8");
56
+ config = JSON.parse(content);
57
+ console.log(" Found existing vercel.json");
58
+ } else {
59
+ console.log(" Creating vercel.json");
60
+ }
61
+ if (config.headers) {
62
+ const hasFrameHeaders = config.headers.some(
63
+ (h) => h.headers?.some(
64
+ (header) => header.key === "X-Frame-Options" || header.key === "Content-Security-Policy"
65
+ )
66
+ );
67
+ if (hasFrameHeaders) {
68
+ console.log(" \u2713 Headers already configured in vercel.json");
69
+ return true;
70
+ }
71
+ config.headers.push(...VERCEL_HEADERS.headers);
72
+ } else {
73
+ config.headers = VERCEL_HEADERS.headers;
74
+ }
75
+ fs.writeFileSync(vercelJsonPath, JSON.stringify(config, null, 2));
76
+ console.log(" \u2713 Added iframe headers to vercel.json");
77
+ return true;
78
+ } catch (error) {
79
+ console.error(" \u2717 Failed to update vercel.json:", error);
80
+ return false;
81
+ }
82
+ }
83
+ function printManualInstructions() {
84
+ console.log(`
85
+ \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
86
+ \u2502 Manual Configuration Required \u2502
87
+ \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524
88
+ \u2502 \u2502
89
+ \u2502 Add this to your vercel.json: \u2502
90
+ \u2502 \u2502
91
+ \u2502 { \u2502
92
+ \u2502 "headers": [ \u2502
93
+ \u2502 { \u2502
94
+ \u2502 "source": "/(.*)", \u2502
95
+ \u2502 "headers": [ \u2502
96
+ \u2502 { "key": "X-Frame-Options", "value": "ALLOWALL" },
97
+ \u2502 { "key": "Content-Security-Policy", \u2502
98
+ \u2502 "value": "frame-ancestors *" } \u2502
99
+ \u2502 ] \u2502
100
+ \u2502 } \u2502
101
+ \u2502 ] \u2502
102
+ \u2502 } \u2502
103
+ \u2502 \u2502
104
+ \u2502 Or add headers() to next.config.js: \u2502
105
+ \u2502 \u2502
106
+ \u2502 async headers() { \u2502
107
+ \u2502 return [{ \u2502
108
+ \u2502 source: '/(.*)', \u2502
109
+ \u2502 headers: [ \u2502
110
+ \u2502 { key: 'X-Frame-Options', value: 'ALLOWALL' }, \u2502
111
+ \u2502 { key: 'Content-Security-Policy', \u2502
112
+ \u2502 value: 'frame-ancestors *' } \u2502
113
+ \u2502 ] \u2502
114
+ \u2502 }]; \u2502
115
+ \u2502 } \u2502
116
+ \u2502 \u2502
117
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
118
+ `);
119
+ }
120
+ function main() {
121
+ const args = process.argv.slice(2);
122
+ const command = args[0];
123
+ console.log("\n\u{1F680} OpenCloud SDK Setup\n");
124
+ if (command === "setup" || !command) {
125
+ const projectRoot = findProjectRoot();
126
+ console.log(`Project root: ${projectRoot}
127
+ `);
128
+ console.log("Configuring iframe headers for OpenCloud...\n");
129
+ const success = setupVercelJson(projectRoot);
130
+ if (success) {
131
+ console.log(`
132
+ \u2705 Setup complete!
133
+
134
+ Your app is now configured to work with OpenCloud.
135
+ After deploying, your app will be embeddable in the OpenCloud marketplace.
136
+
137
+ Next steps:
138
+ 1. Deploy your app: vercel deploy --prod
139
+ 2. Publish on OpenCloud: https://opencloud.app/publish
140
+ `);
141
+ } else {
142
+ printManualInstructions();
143
+ }
144
+ } else if (command === "help" || command === "--help" || command === "-h") {
145
+ console.log(`
146
+ Usage: opencloud-sdk [command]
147
+
148
+ Commands:
149
+ setup Configure your project for OpenCloud (default)
150
+ help Show this help message
151
+
152
+ Examples:
153
+ npx opencloud-platform-sdk setup
154
+ npx opencloud-platform-sdk
155
+ `);
156
+ } else {
157
+ console.log(`Unknown command: ${command}`);
158
+ console.log('Run "opencloud-sdk help" for usage information.');
159
+ }
160
+ }
161
+ main();
package/dist/cli.mjs ADDED
@@ -0,0 +1,138 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli.ts
4
+ import * as fs from "fs";
5
+ import * as path from "path";
6
+ var VERCEL_HEADERS = {
7
+ headers: [
8
+ {
9
+ source: "/(.*)",
10
+ headers: [
11
+ { key: "X-Frame-Options", value: "ALLOWALL" },
12
+ { key: "Content-Security-Policy", value: "frame-ancestors *" }
13
+ ]
14
+ }
15
+ ]
16
+ };
17
+ function findProjectRoot() {
18
+ let dir = process.cwd();
19
+ while (dir !== path.dirname(dir)) {
20
+ if (fs.existsSync(path.join(dir, "package.json"))) {
21
+ return dir;
22
+ }
23
+ dir = path.dirname(dir);
24
+ }
25
+ return process.cwd();
26
+ }
27
+ function setupVercelJson(projectRoot) {
28
+ const vercelJsonPath = path.join(projectRoot, "vercel.json");
29
+ try {
30
+ let config = {};
31
+ if (fs.existsSync(vercelJsonPath)) {
32
+ const content = fs.readFileSync(vercelJsonPath, "utf-8");
33
+ config = JSON.parse(content);
34
+ console.log(" Found existing vercel.json");
35
+ } else {
36
+ console.log(" Creating vercel.json");
37
+ }
38
+ if (config.headers) {
39
+ const hasFrameHeaders = config.headers.some(
40
+ (h) => h.headers?.some(
41
+ (header) => header.key === "X-Frame-Options" || header.key === "Content-Security-Policy"
42
+ )
43
+ );
44
+ if (hasFrameHeaders) {
45
+ console.log(" \u2713 Headers already configured in vercel.json");
46
+ return true;
47
+ }
48
+ config.headers.push(...VERCEL_HEADERS.headers);
49
+ } else {
50
+ config.headers = VERCEL_HEADERS.headers;
51
+ }
52
+ fs.writeFileSync(vercelJsonPath, JSON.stringify(config, null, 2));
53
+ console.log(" \u2713 Added iframe headers to vercel.json");
54
+ return true;
55
+ } catch (error) {
56
+ console.error(" \u2717 Failed to update vercel.json:", error);
57
+ return false;
58
+ }
59
+ }
60
+ function printManualInstructions() {
61
+ console.log(`
62
+ \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
63
+ \u2502 Manual Configuration Required \u2502
64
+ \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524
65
+ \u2502 \u2502
66
+ \u2502 Add this to your vercel.json: \u2502
67
+ \u2502 \u2502
68
+ \u2502 { \u2502
69
+ \u2502 "headers": [ \u2502
70
+ \u2502 { \u2502
71
+ \u2502 "source": "/(.*)", \u2502
72
+ \u2502 "headers": [ \u2502
73
+ \u2502 { "key": "X-Frame-Options", "value": "ALLOWALL" },
74
+ \u2502 { "key": "Content-Security-Policy", \u2502
75
+ \u2502 "value": "frame-ancestors *" } \u2502
76
+ \u2502 ] \u2502
77
+ \u2502 } \u2502
78
+ \u2502 ] \u2502
79
+ \u2502 } \u2502
80
+ \u2502 \u2502
81
+ \u2502 Or add headers() to next.config.js: \u2502
82
+ \u2502 \u2502
83
+ \u2502 async headers() { \u2502
84
+ \u2502 return [{ \u2502
85
+ \u2502 source: '/(.*)', \u2502
86
+ \u2502 headers: [ \u2502
87
+ \u2502 { key: 'X-Frame-Options', value: 'ALLOWALL' }, \u2502
88
+ \u2502 { key: 'Content-Security-Policy', \u2502
89
+ \u2502 value: 'frame-ancestors *' } \u2502
90
+ \u2502 ] \u2502
91
+ \u2502 }]; \u2502
92
+ \u2502 } \u2502
93
+ \u2502 \u2502
94
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
95
+ `);
96
+ }
97
+ function main() {
98
+ const args = process.argv.slice(2);
99
+ const command = args[0];
100
+ console.log("\n\u{1F680} OpenCloud SDK Setup\n");
101
+ if (command === "setup" || !command) {
102
+ const projectRoot = findProjectRoot();
103
+ console.log(`Project root: ${projectRoot}
104
+ `);
105
+ console.log("Configuring iframe headers for OpenCloud...\n");
106
+ const success = setupVercelJson(projectRoot);
107
+ if (success) {
108
+ console.log(`
109
+ \u2705 Setup complete!
110
+
111
+ Your app is now configured to work with OpenCloud.
112
+ After deploying, your app will be embeddable in the OpenCloud marketplace.
113
+
114
+ Next steps:
115
+ 1. Deploy your app: vercel deploy --prod
116
+ 2. Publish on OpenCloud: https://opencloud.app/publish
117
+ `);
118
+ } else {
119
+ printManualInstructions();
120
+ }
121
+ } else if (command === "help" || command === "--help" || command === "-h") {
122
+ console.log(`
123
+ Usage: opencloud-sdk [command]
124
+
125
+ Commands:
126
+ setup Configure your project for OpenCloud (default)
127
+ help Show this help message
128
+
129
+ Examples:
130
+ npx opencloud-platform-sdk setup
131
+ npx opencloud-platform-sdk
132
+ `);
133
+ } else {
134
+ console.log(`Unknown command: ${command}`);
135
+ console.log('Run "opencloud-sdk help" for usage information.');
136
+ }
137
+ }
138
+ main();