frontier-os-app-builder 1.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 +92 -0
- package/agents/fos-executor.md +460 -0
- package/agents/fos-plan-checker.md +386 -0
- package/agents/fos-planner.md +416 -0
- package/agents/fos-researcher.md +358 -0
- package/agents/fos-verifier.md +491 -0
- package/bin/fos-tools.cjs +794 -0
- package/bin/install.js +234 -0
- package/commands/fos/add-feature.md +29 -0
- package/commands/fos/discuss.md +31 -0
- package/commands/fos/execute.md +35 -0
- package/commands/fos/new-app.md +39 -0
- package/commands/fos/new-milestone.md +28 -0
- package/commands/fos/next.md +29 -0
- package/commands/fos/plan.md +37 -0
- package/commands/fos/ship.md +29 -0
- package/commands/fos/status.md +22 -0
- package/package.json +30 -0
- package/references/app-patterns.md +501 -0
- package/references/deployment.md +395 -0
- package/references/module-inference.md +349 -0
- package/references/sdk-surface.md +1622 -0
- package/references/verification-rules.md +404 -0
- package/templates/app/gitignore +25 -0
- package/templates/app/index.css +111 -0
- package/templates/app/index.html +19 -0
- package/templates/app/layout.tsx +45 -0
- package/templates/app/main-router.tsx +17 -0
- package/templates/app/main-simple.tsx +19 -0
- package/templates/app/package.json +36 -0
- package/templates/app/postcss.config.js +5 -0
- package/templates/app/router.tsx +22 -0
- package/templates/app/sdk-context.tsx +33 -0
- package/templates/app/test-setup.ts +19 -0
- package/templates/app/tsconfig.json +22 -0
- package/templates/app/vercel.json +127 -0
- package/templates/app/vite.config.ts +15 -0
- package/templates/state/context.md +248 -0
- package/templates/state/manifest.json +11 -0
- package/templates/state/plan.md +187 -0
- package/templates/state/project.md +118 -0
- package/templates/state/requirements.md +133 -0
- package/templates/state/roadmap.md +129 -0
- package/templates/state/state.md +131 -0
- package/templates/state/summary.md +273 -0
- package/workflows/add-feature.md +234 -0
- package/workflows/discuss.md +310 -0
- package/workflows/execute-plan.md +222 -0
- package/workflows/execute.md +338 -0
- package/workflows/new-app.md +331 -0
- package/workflows/new-milestone.md +258 -0
- package/workflows/next.md +157 -0
- package/workflows/plan.md +310 -0
- package/workflows/ship.md +296 -0
- package/workflows/status.md +145 -0
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
# Deployment Reference
|
|
2
|
+
|
|
3
|
+
Complete deployment guide for Frontier OS apps, covering Vercel configuration, CORS, app registration, environment variables, DNS, and webhooks.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Vercel Deployment Configuration
|
|
8
|
+
|
|
9
|
+
All Frontier OS apps deploy to Vercel. The `vercel.json` file is identical across all apps.
|
|
10
|
+
|
|
11
|
+
### `vercel.json` (Exact)
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"rewrites": [
|
|
16
|
+
{ "source": "/(.*)", "destination": "/index.html" }
|
|
17
|
+
],
|
|
18
|
+
"headers": [
|
|
19
|
+
{
|
|
20
|
+
"source": "/(.*)",
|
|
21
|
+
"has": [
|
|
22
|
+
{
|
|
23
|
+
"type": "header",
|
|
24
|
+
"key": "Origin",
|
|
25
|
+
"value": "https://os.frontiertower.io"
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
"headers": [
|
|
29
|
+
{
|
|
30
|
+
"key": "Access-Control-Allow-Origin",
|
|
31
|
+
"value": "https://os.frontiertower.io"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"key": "Access-Control-Allow-Methods",
|
|
35
|
+
"value": "GET, OPTIONS"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"key": "Access-Control-Allow-Headers",
|
|
39
|
+
"value": "Content-Type"
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"source": "/(.*)",
|
|
45
|
+
"has": [
|
|
46
|
+
{
|
|
47
|
+
"type": "header",
|
|
48
|
+
"key": "Origin",
|
|
49
|
+
"value": "https://alpha.os.frontiertower.io"
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
"headers": [
|
|
53
|
+
{
|
|
54
|
+
"key": "Access-Control-Allow-Origin",
|
|
55
|
+
"value": "https://alpha.os.frontiertower.io"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"key": "Access-Control-Allow-Methods",
|
|
59
|
+
"value": "GET, OPTIONS"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"key": "Access-Control-Allow-Headers",
|
|
63
|
+
"value": "Content-Type"
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"source": "/(.*)",
|
|
69
|
+
"has": [
|
|
70
|
+
{
|
|
71
|
+
"type": "header",
|
|
72
|
+
"key": "Origin",
|
|
73
|
+
"value": "https://beta.os.frontiertower.io"
|
|
74
|
+
}
|
|
75
|
+
],
|
|
76
|
+
"headers": [
|
|
77
|
+
{
|
|
78
|
+
"key": "Access-Control-Allow-Origin",
|
|
79
|
+
"value": "https://beta.os.frontiertower.io"
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"key": "Access-Control-Allow-Methods",
|
|
83
|
+
"value": "GET, OPTIONS"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"key": "Access-Control-Allow-Headers",
|
|
87
|
+
"value": "Content-Type"
|
|
88
|
+
}
|
|
89
|
+
]
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"source": "/(.*)",
|
|
93
|
+
"has": [
|
|
94
|
+
{
|
|
95
|
+
"type": "header",
|
|
96
|
+
"key": "Origin",
|
|
97
|
+
"value": "https://sandbox.os.frontiertower.io"
|
|
98
|
+
}
|
|
99
|
+
],
|
|
100
|
+
"headers": [
|
|
101
|
+
{
|
|
102
|
+
"key": "Access-Control-Allow-Origin",
|
|
103
|
+
"value": "https://sandbox.os.frontiertower.io"
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"key": "Access-Control-Allow-Methods",
|
|
107
|
+
"value": "GET, OPTIONS"
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"key": "Access-Control-Allow-Headers",
|
|
111
|
+
"value": "Content-Type"
|
|
112
|
+
}
|
|
113
|
+
]
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"source": "/(.*)",
|
|
117
|
+
"has": [
|
|
118
|
+
{
|
|
119
|
+
"type": "header",
|
|
120
|
+
"key": "Origin",
|
|
121
|
+
"value": "http://localhost:5173"
|
|
122
|
+
}
|
|
123
|
+
],
|
|
124
|
+
"headers": [
|
|
125
|
+
{
|
|
126
|
+
"key": "Access-Control-Allow-Origin",
|
|
127
|
+
"value": "http://localhost:5173"
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"key": "Access-Control-Allow-Methods",
|
|
131
|
+
"value": "GET, OPTIONS"
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"key": "Access-Control-Allow-Headers",
|
|
135
|
+
"value": "Content-Type"
|
|
136
|
+
}
|
|
137
|
+
]
|
|
138
|
+
}
|
|
139
|
+
]
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Why 5 Separate Blocks
|
|
144
|
+
|
|
145
|
+
Vercel does not support multiple values in a single `Access-Control-Allow-Origin` header. Each allowed origin requires its own conditional header block using the `has` matcher. The `has` condition checks the incoming `Origin` request header and only attaches the CORS response headers when the origin matches.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Allowed Origins
|
|
150
|
+
|
|
151
|
+
The Frontier Wallet PWA runs at these 5 origins. Apps must allow CORS from all of them:
|
|
152
|
+
|
|
153
|
+
| Origin | Environment | Description |
|
|
154
|
+
| ----------------------------------------- | ------------ | -------------------------------------------------------------- |
|
|
155
|
+
| `http://localhost:5173` | Development | Local Vite dev server for the PWA |
|
|
156
|
+
| `https://sandbox.os.frontiertower.io` | Sandbox | Sandbox environment |
|
|
157
|
+
| `https://alpha.os.frontiertower.io` | Alpha | Early access, design preview -- "there will be dragons" |
|
|
158
|
+
| `https://beta.os.frontiertower.io` | Beta | Internally QA'd and tested, no external audit |
|
|
159
|
+
| `https://os.frontiertower.io` | Production | Production ready |
|
|
160
|
+
|
|
161
|
+
These origins are also hardcoded in the SDK at `@frontiertower/frontier-sdk/ui-utils/detection.ts` as `ALLOWED_ORIGINS`.
|
|
162
|
+
|
|
163
|
+
The `isInFrontierApp()` function checks `window.self !== window.top` to detect if the app is running inside the Frontier Wallet iframe. The `getParentOrigin()` function resolves the parent frame's origin via `document.referrer` or `window.parent.location.origin`.
|
|
164
|
+
|
|
165
|
+
**Never use `Access-Control-Allow-Origin: *` in production.** Always use the exact origin list above.
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Security Headers
|
|
170
|
+
|
|
171
|
+
In addition to CORS, all deployment targets should include:
|
|
172
|
+
|
|
173
|
+
| Header | Recommended Value | Purpose |
|
|
174
|
+
| ---------------------------- | -------------------------------------------------- | ------------------------------------------- |
|
|
175
|
+
| `Content-Security-Policy` | Include `frame-ancestors` for all 5 Frontier origins | Prevents embedding by unauthorized sites |
|
|
176
|
+
| `X-Content-Type-Options` | `nosniff` | Prevents MIME-type sniffing |
|
|
177
|
+
| `Referrer-Policy` | `strict-origin-when-cross-origin` | Controls referrer information leakage |
|
|
178
|
+
| `Permissions-Policy` | Disable unnecessary browser APIs | Reduces attack surface |
|
|
179
|
+
|
|
180
|
+
The `frame-ancestors` CSP directive is critical because apps load inside the PWA's iframe. Without it, the browser may block the embed.
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## App Registration
|
|
185
|
+
|
|
186
|
+
Apps must be registered with the Frontier platform before they appear in the AppStore.
|
|
187
|
+
|
|
188
|
+
### Registration via ThirdParty SDK
|
|
189
|
+
|
|
190
|
+
The recommended way is to use the OS Developer app in the Frontier AppStore. The API alternative:
|
|
191
|
+
|
|
192
|
+
1. **Get developer access** -- contact support@frontiertower.io to be added as a developer manager.
|
|
193
|
+
2. **Get your developer profile** -- install the OS Developer app from the AppStore, or call `GET /third-party/developers/`.
|
|
194
|
+
3. **Rotate your API key** immediately after receiving it: `POST /third-party/developers/{developer_id}/rotate-key/`. Store the new key securely.
|
|
195
|
+
4. **Create the app** -- via OS Developer or `POST /third-party/apps/` with metadata: `name`, `url`, `description`, optional DNS entries.
|
|
196
|
+
|
|
197
|
+
### App Status Lifecycle
|
|
198
|
+
|
|
199
|
+
```
|
|
200
|
+
in_review --> accepted --> released
|
|
201
|
+
^ |
|
|
202
|
+
|--- (any update) |
|
|
203
|
+
v
|
|
204
|
+
request_deactivation --> deactivated
|
|
205
|
+
|
|
206
|
+
in_review --> rejected
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
- New apps start in `in_review`.
|
|
210
|
+
- Updating an app returns it to `in_review`.
|
|
211
|
+
- Deleting a released app sets `request_deactivation` (no hard-delete).
|
|
212
|
+
|
|
213
|
+
### Permissions in Registry
|
|
214
|
+
|
|
215
|
+
When registering, declare the SDK permissions your app requires:
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
const APP_REGISTRY: AppMetadata[] = [
|
|
219
|
+
{
|
|
220
|
+
id: 'my-app',
|
|
221
|
+
url: 'https://my-app.appstore.frontiertower.io',
|
|
222
|
+
origin: 'https://my-app.appstore.frontiertower.io',
|
|
223
|
+
version: '1.0.0',
|
|
224
|
+
developer: {
|
|
225
|
+
name: 'Developer Name',
|
|
226
|
+
verified: true,
|
|
227
|
+
},
|
|
228
|
+
permissions: {
|
|
229
|
+
wallet: true,
|
|
230
|
+
storage: true,
|
|
231
|
+
notifications: false,
|
|
232
|
+
},
|
|
233
|
+
} as AppMetadata,
|
|
234
|
+
];
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Permissions must match the SDK methods actually called in source code. The fos-verifier agent enforces this (see [verification-rules.md](verification-rules.md)).
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## Environment Variables
|
|
242
|
+
|
|
243
|
+
Frontier OS apps use the standard Vite environment variable pattern.
|
|
244
|
+
|
|
245
|
+
### `.env.local` (not committed)
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
VITE_APP_NAME=My App
|
|
249
|
+
VITE_API_URL=https://api.example.com
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Convention
|
|
253
|
+
|
|
254
|
+
- All client-exposed variables must use the `VITE_` prefix.
|
|
255
|
+
- Access in code via `import.meta.env.VITE_APP_NAME`.
|
|
256
|
+
- `.env.local` is gitignored. Never commit secrets.
|
|
257
|
+
- For Vercel deployment, set environment variables in the Vercel dashboard under Project Settings > Environment Variables.
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
## DNS Configuration
|
|
262
|
+
|
|
263
|
+
Apps are hosted on the `appstore.frontiertower.io` subdomain.
|
|
264
|
+
|
|
265
|
+
### Domain Pattern
|
|
266
|
+
|
|
267
|
+
```
|
|
268
|
+
<app-name>.appstore.frontiertower.io
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Example: `kickstarter.appstore.frontiertower.io`
|
|
272
|
+
|
|
273
|
+
### DNS Entries
|
|
274
|
+
|
|
275
|
+
Two DNS records are needed:
|
|
276
|
+
|
|
277
|
+
1. **CNAME** -- points the subdomain to the Vercel deployment:
|
|
278
|
+
```
|
|
279
|
+
<app-name>.appstore.frontiertower.io CNAME cname.vercel-dns.com
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
2. **TXT** -- Vercel domain verification:
|
|
283
|
+
```
|
|
284
|
+
_vercel.<app-name>.appstore.frontiertower.io TXT vc-domain-verify=<token>
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Configure the custom domain in the Vercel dashboard after DNS propagation. Vercel automatically provisions an SSL certificate.
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## Webhook Setup
|
|
292
|
+
|
|
293
|
+
Webhooks let the Frontier API push events to your backend the moment something changes.
|
|
294
|
+
|
|
295
|
+
### Creating a Webhook
|
|
296
|
+
|
|
297
|
+
1. Use the OS Developer app or call `POST /third-party/webhooks/`.
|
|
298
|
+
2. Provide:
|
|
299
|
+
```json
|
|
300
|
+
{
|
|
301
|
+
"name": "My app events",
|
|
302
|
+
"target_url": "https://my-backend.example.com/webhooks",
|
|
303
|
+
"config": {
|
|
304
|
+
"events": ["event:*"],
|
|
305
|
+
"scope": { "communities": [10], "users": "*" }
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
3. New webhooks start in `IN_REVIEW`. After review, they become `LIVE` and start receiving deliveries.
|
|
310
|
+
|
|
311
|
+
### Webhook Lifecycle
|
|
312
|
+
|
|
313
|
+
- `IN_REVIEW` -- default after creation or config changes
|
|
314
|
+
- `LIVE` -- approved, receiving deliveries
|
|
315
|
+
- `REJECTED` -- not approved
|
|
316
|
+
|
|
317
|
+
### Event Format
|
|
318
|
+
|
|
319
|
+
Events use `namespace:action` format. Subscriptions support wildcards (`event:*` or `*`).
|
|
320
|
+
|
|
321
|
+
Available namespaces: `addon_product`, `addon`, `community`, `guest_check_in`, `internship_pass`, `supply_request`, `event`, `location`, `room_booking`, `sponsor`, `sponsor_pass`, `smart_account`, `bridge_account`, `recovery_request`, `post`, `citizen_suggestion`, `vote`, `developer`, `app`, `webhook`, `user`, `profile`, `subscription`.
|
|
322
|
+
|
|
323
|
+
### Delivery Payload
|
|
324
|
+
|
|
325
|
+
Each delivery is an HTTP POST:
|
|
326
|
+
|
|
327
|
+
```json
|
|
328
|
+
{
|
|
329
|
+
"id": "{delivery_uuid}",
|
|
330
|
+
"event": "namespace:action",
|
|
331
|
+
"triggered_at": "2024-01-01T12:00:00Z",
|
|
332
|
+
"data": { }
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
Headers on every delivery:
|
|
337
|
+
- `Content-Type: application/json`
|
|
338
|
+
- `X-Webhook-Event`
|
|
339
|
+
- `X-Webhook-Id`
|
|
340
|
+
- `X-Webhook-Timestamp`
|
|
341
|
+
- `X-Webhook-Signature`
|
|
342
|
+
- `X-Webhook-Signature-Algorithm: ed25519`
|
|
343
|
+
|
|
344
|
+
### Signature Verification (Ed25519)
|
|
345
|
+
|
|
346
|
+
Verify every delivery before processing:
|
|
347
|
+
|
|
348
|
+
1. Read `X-Webhook-Timestamp` from the request.
|
|
349
|
+
2. Build the message: `<timestamp>.<canonical_json_body>` (JSON with sorted keys, no extra spaces).
|
|
350
|
+
3. Verify `X-Webhook-Signature` (base64-encoded) using the webhook's Ed25519 public key.
|
|
351
|
+
4. Reject if verification fails, timestamp is stale, or event is unexpected.
|
|
352
|
+
|
|
353
|
+
```python
|
|
354
|
+
import base64, json
|
|
355
|
+
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
|
|
356
|
+
|
|
357
|
+
body = request.get_json(force=True)
|
|
358
|
+
timestamp = request.headers["X-Webhook-Timestamp"]
|
|
359
|
+
signature = base64.b64decode(request.headers["X-Webhook-Signature"])
|
|
360
|
+
message = f"{timestamp}.".encode() + json.dumps(body, separators=(",", ":"), sort_keys=True).encode()
|
|
361
|
+
|
|
362
|
+
public_key = Ed25519PublicKey.from_public_bytes(base64.b64decode(PUBLIC_KEY))
|
|
363
|
+
public_key.verify(signature, message) # Raises if invalid
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### Rotating Webhook Signing Keys
|
|
367
|
+
|
|
368
|
+
Call `POST /third-party/webhooks/{webhook_id}/rotate-key/`. Deliveries immediately use the new key. Always trust the latest public key for each webhook.
|
|
369
|
+
|
|
370
|
+
### Operational Tips
|
|
371
|
+
|
|
372
|
+
- Use HTTPS endpoints only.
|
|
373
|
+
- Treat `X-Webhook-Id` as an idempotency key.
|
|
374
|
+
- Keep one webhook per environment (staging vs production).
|
|
375
|
+
- Rotate signing keys and API keys regularly.
|
|
376
|
+
|
|
377
|
+
---
|
|
378
|
+
|
|
379
|
+
## Testing Deployment
|
|
380
|
+
|
|
381
|
+
After deploying, verify CORS is working:
|
|
382
|
+
|
|
383
|
+
```bash
|
|
384
|
+
curl -I https://<app-name>.appstore.frontiertower.io \
|
|
385
|
+
-H "Origin: https://os.frontiertower.io"
|
|
386
|
+
|
|
387
|
+
# Should include:
|
|
388
|
+
# Access-Control-Allow-Origin: https://os.frontiertower.io
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
Verify the app loads correctly:
|
|
392
|
+
1. Check browser console for CORS errors.
|
|
393
|
+
2. Confirm the app is accessible directly at its URL.
|
|
394
|
+
3. Test inside the Frontier Wallet PWA across environments.
|
|
395
|
+
4. Verify metadata (`<title>`, `<meta name="description">`, `<link rel="icon">`) is present in the HTML.
|