frontier-os-app-builder 1.1.0 → 1.2.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.
Files changed (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +25 -0
  3. package/agents/fos-executor.md +22 -65
  4. package/agents/fos-plan-checker.md +13 -12
  5. package/agents/fos-planner.md +20 -67
  6. package/agents/fos-researcher.md +14 -10
  7. package/agents/fos-verifier.md +11 -5
  8. package/bin/fos-tools.cjs +48 -11
  9. package/bin/install.js +8 -5
  10. package/commands/fos/add-feature.md +1 -2
  11. package/commands/fos/discuss.md +0 -1
  12. package/commands/fos/new-app.md +1 -3
  13. package/commands/fos/new-milestone.md +1 -1
  14. package/commands/fos/plan.md +0 -2
  15. package/package.json +7 -1
  16. package/references/app-patterns.md +46 -28
  17. package/references/deployment.md +40 -74
  18. package/references/module-index.md +32 -0
  19. package/references/sdk/chain.md +92 -0
  20. package/references/sdk/communities.md +159 -0
  21. package/references/sdk/events.md +212 -0
  22. package/references/sdk/init.md +126 -0
  23. package/references/sdk/navigation.md +49 -0
  24. package/references/sdk/offices.md +76 -0
  25. package/references/sdk/partnerships.md +111 -0
  26. package/references/sdk/storage.md +44 -0
  27. package/references/sdk/thirdparty.md +240 -0
  28. package/references/sdk/token-amount.md +99 -0
  29. package/references/sdk/types.md +27 -0
  30. package/references/sdk/ui-utils.md +39 -0
  31. package/references/sdk/user.md +208 -0
  32. package/references/sdk/wallet.md +334 -0
  33. package/references/verification-rules.md +18 -18
  34. package/templates/app/frontier-services.tsx +75 -18
  35. package/templates/app/layout.tsx +19 -9
  36. package/templates/app/package.json +2 -1
  37. package/templates/app/public/favicon.svg +3 -0
  38. package/templates/app/sdk-context.tsx +7 -9
  39. package/templates/app/sdk-services.tsx +92 -117
  40. package/templates/app/vercel.json +8 -47
  41. package/templates/state/plan.md +32 -14
  42. package/templates/state/roadmap.md +2 -2
  43. package/templates/state/summary.md +26 -29
  44. package/workflows/add-feature.md +6 -1
  45. package/workflows/discuss.md +9 -3
  46. package/workflows/execute-plan.md +3 -3
  47. package/workflows/execute.md +17 -6
  48. package/workflows/new-app.md +54 -18
  49. package/workflows/new-milestone.md +9 -2
  50. package/workflows/plan.md +14 -5
  51. package/workflows/ship.md +26 -10
  52. package/workflows/status.md +0 -1
  53. package/references/module-inference.md +0 -348
  54. package/references/sdk-surface.md +0 -1600
  55. package/templates/app/main-simple-standalone.tsx +0 -19
  56. package/templates/app/main-simple.tsx +0 -19
  57. package/templates/state/manifest.json +0 -12
@@ -18,13 +18,6 @@ All Frontier OS apps deploy to Vercel. The `vercel.json` file is identical acros
18
18
  "headers": [
19
19
  {
20
20
  "source": "/(.*)",
21
- "has": [
22
- {
23
- "type": "header",
24
- "key": "Origin",
25
- "value": "https://os.frontiertower.io"
26
- }
27
- ],
28
21
  "headers": [
29
22
  {
30
23
  "key": "Access-Control-Allow-Origin",
@@ -37,54 +30,22 @@ All Frontier OS apps deploy to Vercel. The `vercel.json` file is identical acros
37
30
  {
38
31
  "key": "Access-Control-Allow-Headers",
39
32
  "value": "Content-Type"
40
- }
41
- ]
42
- },
43
- {
44
- "source": "/(.*)",
45
- "has": [
46
- {
47
- "type": "header",
48
- "key": "Origin",
49
- "value": "https://sandbox.os.frontiertower.io"
50
- }
51
- ],
52
- "headers": [
53
- {
54
- "key": "Access-Control-Allow-Origin",
55
- "value": "https://sandbox.os.frontiertower.io"
56
33
  },
57
34
  {
58
- "key": "Access-Control-Allow-Methods",
59
- "value": "GET, OPTIONS"
35
+ "key": "Content-Security-Policy",
36
+ "value": "frame-ancestors https://os.frontiertower.io https://sandbox.os.frontiertower.io http://localhost:5173;"
60
37
  },
61
38
  {
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": "http://localhost:5173"
74
- }
75
- ],
76
- "headers": [
77
- {
78
- "key": "Access-Control-Allow-Origin",
79
- "value": "http://localhost:5173"
39
+ "key": "X-Content-Type-Options",
40
+ "value": "nosniff"
80
41
  },
81
42
  {
82
- "key": "Access-Control-Allow-Methods",
83
- "value": "GET, OPTIONS"
43
+ "key": "Referrer-Policy",
44
+ "value": "strict-origin-when-cross-origin"
84
45
  },
85
46
  {
86
- "key": "Access-Control-Allow-Headers",
87
- "value": "Content-Type"
47
+ "key": "Permissions-Policy",
48
+ "value": "camera=(), microphone=(), geolocation=()"
88
49
  }
89
50
  ]
90
51
  }
@@ -92,15 +53,15 @@ All Frontier OS apps deploy to Vercel. The `vercel.json` file is identical acros
92
53
  }
93
54
  ```
94
55
 
95
- ### Why 3 Separate Blocks
56
+ ### One Header Block + CSP `frame-ancestors`
96
57
 
97
- 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.
58
+ A single unconditional header block applies CORS for the production origin plus the security headers to every response. Embedding by all three Frontier origins is granted by the `Content-Security-Policy: frame-ancestors` directive (which lists all three) `frame-ancestors` is what actually governs iframe embedding, not per-origin CORS reflection. This replaces the older pattern of one `has`-matched CORS block per origin.
98
59
 
99
60
  ---
100
61
 
101
62
  ## Allowed Origins
102
63
 
103
- The Frontier Wallet PWA runs at these 3 origins. Apps must allow CORS from all of them:
64
+ The Frontier Wallet PWA runs at these 3 origins. Apps must allow all of them to embed the app via the CSP `frame-ancestors` directive:
104
65
 
105
66
  | Origin | Environment | Description |
106
67
  | ----------------------------------------- | ------------ | -------------------------------------------------------------- |
@@ -108,7 +69,7 @@ The Frontier Wallet PWA runs at these 3 origins. Apps must allow CORS from all o
108
69
  | `https://sandbox.os.frontiertower.io` | Sandbox | Sandbox environment |
109
70
  | `https://os.frontiertower.io` | Production | Production ready |
110
71
 
111
- These origins are also hardcoded in the SDK at `@frontiertower/frontier-sdk/ui-utils/detection.ts` as `ALLOWED_ORIGINS`.
72
+ These origins are also hardcoded in the SDK at `@frontiertower/frontier-sdk/ui-utils/detection.ts` as `ALLOWED_ORIGINS` (exactly these 3). Note: `isInFrontierApp()` no longer consults this list -- it returns `window.self !== window.top` -- so `ALLOWED_ORIGINS` is informational for CORS/CSP only.
112
73
 
113
74
  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`.
114
75
 
@@ -118,16 +79,16 @@ The `isInFrontierApp()` function checks `window.self !== window.top` to detect i
118
79
 
119
80
  ## Security Headers
120
81
 
121
- In addition to CORS, all deployment targets should include:
82
+ The `vercel.json` above already ships these alongside CORS every app should keep them:
122
83
 
123
- | Header | Recommended Value | Purpose |
84
+ | Header | Value (shipped in vercel.json) | Purpose |
124
85
  | ---------------------------- | -------------------------------------------------- | ------------------------------------------- |
125
- | `Content-Security-Policy` | Include `frame-ancestors` for all 3 Frontier origins | Prevents embedding by unauthorized sites |
86
+ | `Content-Security-Policy` | `frame-ancestors https://os.frontiertower.io https://sandbox.os.frontiertower.io http://localhost:5173;` (the 3 live origins) | Restricts who may embed the app |
126
87
  | `X-Content-Type-Options` | `nosniff` | Prevents MIME-type sniffing |
127
88
  | `Referrer-Policy` | `strict-origin-when-cross-origin` | Controls referrer information leakage |
128
- | `Permissions-Policy` | Disable unnecessary browser APIs | Reduces attack surface |
89
+ | `Permissions-Policy` | `camera=(), microphone=(), geolocation=()` | Disables unused browser APIs |
129
90
 
130
- The `frame-ancestors` CSP directive is critical because apps load inside the PWA's iframe. Without it, the browser may block the embed.
91
+ The `frame-ancestors` CSP directive is critical because apps load inside the PWA's iframe. Without it, the browser may block the embed. The CSP is intentionally limited to `frame-ancestors` so it never blocks app resources (e.g. the Google Fonts stylesheet the template loads); apps that self-host all assets may tighten it further.
131
92
 
132
93
  ---
133
94
 
@@ -142,7 +103,7 @@ The recommended way is to use the OS Developer app in the Frontier AppStore. The
142
103
  1. **Get developer access** -- contact support@frontiertower.io to be added as a developer manager.
143
104
  2. **Get your developer profile** -- install the OS Developer app from the AppStore, or call `GET /third-party/developers/`.
144
105
  3. **Rotate your API key** immediately after receiving it: `POST /third-party/developers/{developer_id}/rotate-key/`. Store the new key securely.
145
- 4. **Create the app** -- via OS Developer or `POST /third-party/apps/` with metadata: `name`, `url`, `description`, optional DNS entries.
106
+ 4. **Create the app** -- via OS Developer (`sdk.getThirdParty().createApp(...)`) or `POST /third-party/apps/` with body `{ url, cnameEntry, txtEntry?, permissions: string[], permissionDisclaimer }` (OS Developer additionally sends `developer: <id>`). App `name`, `description`, and `icon` are NOT sent -- they are auto-fetched from the app URL's HTML metadata (`<title>`, `<meta name="description">`, `<link rel="icon">`).
146
107
 
147
108
  ### App Status Lifecycle
148
109
 
@@ -167,24 +128,29 @@ When registering, declare the SDK permissions your app requires:
167
128
  ```typescript
168
129
  const APP_REGISTRY: AppMetadata[] = [
169
130
  {
170
- id: 'my-app',
171
- url: 'https://my-app.appstore.frontiertower.io',
172
- origin: 'https://my-app.appstore.frontiertower.io',
173
- version: '1.0.0',
131
+ id: 'ifnd-converter',
132
+ url: 'https://ifnd-converter.apps.frontiertower.io',
133
+ icon: '/svgs/ifnd_converter.svg',
174
134
  developer: {
175
135
  name: 'Developer Name',
176
- verified: true,
177
- },
178
- permissions: {
179
- wallet: true,
180
- storage: true,
181
- notifications: false,
136
+ url: 'https://frontiertower.io',
137
+ description: 'Made with love by the Frontier Tower Action Team',
182
138
  },
139
+ permissions: [
140
+ 'wallet:getBalance',
141
+ 'wallet:getAddress',
142
+ 'wallet:executeBatchCall',
143
+ 'wallet:executeCall',
144
+ 'chain:getCurrentChainConfig',
145
+ ],
146
+ permissionDisclaimer:
147
+ 'This app accesses your wallet address and balance and executes the conversion calls.',
148
+ // optional: excludedAppStages?, requiresCitizenship?, requiresAdmin?
183
149
  } as AppMetadata,
184
150
  ];
185
151
  ```
186
152
 
187
- Permissions must match the SDK methods actually called in source code. The fos-verifier agent enforces this (see [verification-rules.md](verification-rules.md)).
153
+ Permissions are a flat array of `module:method` strings -- each entry must match an SDK method actually called in source code. `name`, `description`, and `icon` are optional in `AppMetadata` (auto-fetched from the app's HTML when omitted). There is no `origin`, `version`, or `developer.verified` field, and there is no `notifications` permission. The fos-verifier agent enforces the permission-to-source match (see [verification-rules.md](verification-rules.md)).
188
154
 
189
155
  ---
190
156
 
@@ -210,15 +176,15 @@ VITE_API_URL=https://api.example.com
210
176
 
211
177
  ## DNS Configuration
212
178
 
213
- Apps are hosted on the `appstore.frontiertower.io` subdomain.
179
+ Apps are hosted on the `apps.frontiertower.io` subdomain.
214
180
 
215
181
  ### Domain Pattern
216
182
 
217
183
  ```
218
- <app-name>.appstore.frontiertower.io
184
+ <app-name>.apps.frontiertower.io
219
185
  ```
220
186
 
221
- Example: `kickstarter.appstore.frontiertower.io`
187
+ Example: `kickstarter.apps.frontiertower.io`
222
188
 
223
189
  ### DNS Entries
224
190
 
@@ -226,12 +192,12 @@ Two DNS records are needed:
226
192
 
227
193
  1. **CNAME** -- points the subdomain to the Vercel deployment:
228
194
  ```
229
- <app-name>.appstore.frontiertower.io CNAME cname.vercel-dns.com
195
+ <app-name>.apps.frontiertower.io CNAME cname.vercel-dns.com
230
196
  ```
231
197
 
232
198
  2. **TXT** -- Vercel domain verification:
233
199
  ```
234
- _vercel.<app-name>.appstore.frontiertower.io TXT vc-domain-verify=<token>
200
+ _vercel.<app-name>.apps.frontiertower.io TXT vc-domain-verify=<token>
235
201
  ```
236
202
 
237
203
  Configure the custom domain in the Vercel dashboard after DNS propagation. Vercel automatically provisions an SSL certificate.
@@ -331,7 +297,7 @@ Call `POST /third-party/webhooks/{webhook_id}/rotate-key/`. Deliveries immediate
331
297
  After deploying, verify CORS is working:
332
298
 
333
299
  ```bash
334
- curl -I https://<app-name>.appstore.frontiertower.io \
300
+ curl -I https://<app-name>.apps.frontiertower.io \
335
301
  -H "Origin: https://os.frontiertower.io"
336
302
 
337
303
  # Should include:
@@ -0,0 +1,32 @@
1
+ # SDK Module Index
2
+
3
+ Maps app descriptions to Frontier SDK modules. The CLI (`fos-tools.cjs infer-modules`) does keyword matching programmatically — this file documents the algorithm and serves as an index to per-module reference files.
4
+
5
+ ## Inference Algorithm
6
+
7
+ This mirrors `cmdInferModules` in `fos-tools.cjs` exactly:
8
+
9
+ 1. Lowercase the entire description (`description.toLowerCase()`) — no tokenization or word splitting.
10
+ 2. For each SDK module, substring-match its trigger keywords against the lowercased description with `String.includes()` (keywords are listed in each module's reference file under `references/sdk/`). Because matching is by substring, multi-word phrases like `send money`, `access control`, and `api key` work, and a keyword can match inside a larger word (e.g. `fund` matches within `refund`).
11
+ 3. Always include Storage and Chain (the base modules — every app needs these).
12
+ 4. Include User if User's own keywords match, or if any of Wallet, Events, Communities, Partnerships, or Offices matched. User is NOT added by ThirdParty, Navigation, Storage, or Chain alone.
13
+ 5. Present the inferred modules for user confirmation.
14
+
15
+ ## Module Quick Reference
16
+
17
+ | Module | Reference File | Primary Use Case |
18
+ |--------|---------------|-----------------|
19
+ | Wallet | `references/sdk/wallet.md` | Payments, transfers, FND/iFND, fiat on/off-ramp |
20
+ | Storage | `references/sdk/storage.md` | Key-value persistence, preferences |
21
+ | Chain | `references/sdk/chain.md` | Network config, contract addresses |
22
+ | User | `references/sdk/user.md` | Profiles, access controls, membership |
23
+ | Communities | `references/sdk/communities.md` | Groups, internships, member management |
24
+ | Partnerships | `references/sdk/partnerships.md` | Sponsors, passes, brand partnerships |
25
+ | Events | `references/sdk/events.md` | Calendar, rooms, bookings, RSVPs |
26
+ | Offices | `references/sdk/offices.md` | Physical access passes, building entry |
27
+ | ThirdParty | `references/sdk/thirdparty.md` | Developer tools, webhooks, API keys |
28
+ | Navigation | `references/sdk/navigation.md` | Deep links, cross-app navigation |
29
+
30
+ ## After Inference
31
+
32
+ Once modules are confirmed, read only the relevant `references/sdk/<module>.md` files for detailed method signatures, types, and permissions. Always include `references/sdk/init.md` and `references/sdk/types.md` as shared context. For any app that reads, displays, transfers, or swaps FND amounts, also read `references/sdk/token-amount.md` — FND amounts are `bigint` base units bridged to/from display strings via `formatAmount()`/`parseAmount()`.
@@ -0,0 +1,92 @@
1
+ # Chain Module
2
+
3
+ **Trigger keywords:** network, chain, blockchain, contract, smart contract, on-chain, token address, stablecoin
4
+
5
+ Access via `sdk.getChain()`. Query and switch blockchain networks.
6
+
7
+ ---
8
+
9
+ ## Methods
10
+
11
+ ```typescript
12
+ getCurrentNetwork(): Promise<string>
13
+ ```
14
+ Returns the current network identifier (e.g. `'base'`, `'base-sepolia'`). Permission: `chain:getCurrentNetwork`
15
+
16
+ ```typescript
17
+ getAvailableNetworks(): Promise<string[]>
18
+ ```
19
+ Returns all network identifiers the app can switch to. Permission: `chain:getAvailableNetworks`
20
+
21
+ ```typescript
22
+ switchNetwork(network: string): Promise<void>
23
+ ```
24
+ Switch active blockchain network. Affects all subsequent wallet operations. Permission: `chain:switchNetwork`
25
+
26
+ ```typescript
27
+ getCurrentChainConfig(): Promise<ChainConfig>
28
+ ```
29
+ Returns full chain configuration for the current network. Permission: `chain:getCurrentChainConfig`
30
+
31
+ ```typescript
32
+ getContractAddresses(): Promise<{
33
+ fnd: string;
34
+ iFnd: string | null;
35
+ paymentRouter: string;
36
+ subscriptionManager: string;
37
+ }>
38
+ ```
39
+ Returns addresses for FND, iFND (may be null), PaymentRouter, and SubscriptionManager contracts on the current chain. Permission: `chain:getContractAddresses`
40
+
41
+ ---
42
+
43
+ ## Types
44
+
45
+ ```typescript
46
+ enum Underlying {
47
+ USD = "USD",
48
+ }
49
+
50
+ interface Token {
51
+ name: string;
52
+ symbol: string;
53
+ decimals: number;
54
+ address: string;
55
+ }
56
+
57
+ interface StableCoin extends Token {
58
+ underlying: Underlying;
59
+ }
60
+
61
+ interface ChainConfig {
62
+ id: number;
63
+ name: string;
64
+ network: string;
65
+ bridgeSwapRouterFactoryAddress: string;
66
+ uniswapV3FactoryAddress: string;
67
+ nativeCurrency: {
68
+ name: string;
69
+ symbol: string;
70
+ decimals: number;
71
+ };
72
+ blockExplorer: {
73
+ name: string;
74
+ url: string;
75
+ };
76
+ stableCoins: StableCoin[];
77
+ supportedTokens: Token[];
78
+ testnet: boolean;
79
+ }
80
+ ```
81
+
82
+ ---
83
+
84
+ ## Permissions (5)
85
+
86
+ | Permission | Description |
87
+ |---|---|
88
+ | `chain:getCurrentNetwork` | Get current network name |
89
+ | `chain:getAvailableNetworks` | Get list of available networks |
90
+ | `chain:switchNetwork` | Switch to a different network |
91
+ | `chain:getCurrentChainConfig` | Get full chain configuration |
92
+ | `chain:getContractAddresses` | Get FND, iFND, PaymentRouter, SubscriptionManager addresses |
@@ -0,0 +1,159 @@
1
+ # Communities Module
2
+
3
+ **Trigger keywords:** community, group, team, club, internship, intern, cohort, reassign, transfer member, collective, society
4
+
5
+ Access via `sdk.getCommunities()`. Manage communities, internship passes, and member reassignment requests.
6
+
7
+ Community listing is public. Internship passes require authentication, an active subscription, and community manager status. Reassign requests require authentication; creating requires managing the member's current community, accepting requires managing the target community. Superusers can access everything.
8
+
9
+ ---
10
+
11
+ ## Methods
12
+
13
+ ```typescript
14
+ listCommunities(payload?: ListCommunitiesParams): Promise<PaginatedResponse<Community>>
15
+ ```
16
+ List all visible communities, paginated. Permission: `communities:listCommunities`
17
+
18
+ ```typescript
19
+ getCommunity(payload: { idOrSlug: string | number }): Promise<Community>
20
+ ```
21
+ Get a community by numeric ID or slug string. Permission: `communities:getCommunity`
22
+
23
+ ```typescript
24
+ createInternshipPass(payload: CreateInternshipPassRequest): Promise<InternshipPass>
25
+ ```
26
+ Create an internship pass. Auto-creates an inactive account if the user does not exist. Permission: `communities:createInternshipPass`
27
+
28
+ ```typescript
29
+ listInternshipPasses(payload?: ListInternshipPassesParams): Promise<PaginatedResponse<InternshipPass>>
30
+ ```
31
+ List internship passes for managed communities. Active only by default; set `includeRevoked: true` to include revoked. Permission: `communities:listInternshipPasses`
32
+
33
+ ```typescript
34
+ getInternshipPass(payload: { id: number }): Promise<InternshipPass>
35
+ ```
36
+ Get an internship pass by ID. Permission: `communities:getInternshipPass`
37
+
38
+ ```typescript
39
+ revokeInternshipPass(payload: { id: number }): Promise<void>
40
+ ```
41
+ Revoke an internship pass. Cannot revoke an already-revoked pass. Permission: `communities:revokeInternshipPass`
42
+
43
+ ```typescript
44
+ createReassignRequest(payload: CreateReassignRequestPayload): Promise<ReassignRequest>
45
+ ```
46
+ Request to move a member to a different community. Caller must manage the member's current community. Permission: `communities:createReassignRequest`
47
+
48
+ ```typescript
49
+ listReassignRequests(payload?: ListReassignRequestsParams): Promise<PaginatedResponse<ReassignRequest>>
50
+ ```
51
+ List pending reassign requests visible to the caller. Permission: `communities:listReassignRequests`
52
+
53
+ ```typescript
54
+ getReassignRequest(payload: { id: number }): Promise<ReassignRequest>
55
+ ```
56
+ Get a reassign request by ID. Permission: `communities:getReassignRequest`
57
+
58
+ ```typescript
59
+ acceptReassignRequest(payload: { id: number }): Promise<ReassignRequest>
60
+ ```
61
+ Accept a reassign request. Moves the member to the target community. Only target community managers (or superusers) can accept. Permission: `communities:acceptReassignRequest`
62
+
63
+ ```typescript
64
+ rejectReassignRequest(payload: { id: number }): Promise<void>
65
+ ```
66
+ Reject a reassign request. Only pending requests can be rejected. Permission: `communities:rejectReassignRequest`
67
+
68
+ ---
69
+
70
+ ## Types
71
+
72
+ ```typescript
73
+ interface Community {
74
+ id: number;
75
+ name: string;
76
+ description: string;
77
+ slug: string;
78
+ iconName: string;
79
+ splashVideo: string | null;
80
+ }
81
+
82
+ interface ListCommunitiesParams {
83
+ limit?: number;
84
+ offset?: number;
85
+ }
86
+
87
+ type InternshipPassStatus = 'active' | 'revoked';
88
+
89
+ interface InternshipPass {
90
+ id: number;
91
+ email: string;
92
+ firstName: string;
93
+ lastName: string;
94
+ community: number;
95
+ communityName: string;
96
+ status: InternshipPassStatus;
97
+ createdAt: string;
98
+ revokedAt: string | null;
99
+ updatedAt: string;
100
+ }
101
+
102
+ interface CreateInternshipPassRequest {
103
+ email: string;
104
+ firstName: string;
105
+ lastName: string;
106
+ community: number;
107
+ }
108
+
109
+ interface ListInternshipPassesParams {
110
+ limit?: number;
111
+ offset?: number;
112
+ includeRevoked?: boolean;
113
+ }
114
+
115
+ type ReassignRequestStatus = 'pending' | 'accepted' | 'rejected';
116
+
117
+ interface ReassignRequest {
118
+ id: number;
119
+ requester: number;
120
+ requesterEmail: string;
121
+ member: number;
122
+ memberEmail: string;
123
+ targetCommunity: number;
124
+ targetCommunityName: string;
125
+ status: ReassignRequestStatus;
126
+ createdAt: string;
127
+ resolvedAt: string | null;
128
+ resolvedBy: number | null;
129
+ resolvedByEmail: string | null;
130
+ }
131
+
132
+ interface CreateReassignRequestPayload {
133
+ memberEmail: string;
134
+ targetCommunity: number;
135
+ }
136
+
137
+ interface ListReassignRequestsParams {
138
+ limit?: number;
139
+ offset?: number;
140
+ }
141
+ ```
142
+
143
+ ---
144
+
145
+ ## Permissions (11)
146
+
147
+ | Permission | Description |
148
+ |---|---|
149
+ | `communities:listCommunities` | List all visible communities (paginated) |
150
+ | `communities:getCommunity` | Get a community by ID or slug |
151
+ | `communities:createInternshipPass` | Create an internship pass for a managed community |
152
+ | `communities:listInternshipPasses` | List internship passes for managed communities |
153
+ | `communities:getInternshipPass` | Retrieve an internship pass by ID |
154
+ | `communities:revokeInternshipPass` | Revoke an internship pass |
155
+ | `communities:createReassignRequest` | Create a member reassignment request |
156
+ | `communities:listReassignRequests` | List pending reassignment requests |
157
+ | `communities:getReassignRequest` | Retrieve a reassignment request by ID |
158
+ | `communities:acceptReassignRequest` | Accept a reassignment request (moves member) |
159
+ | `communities:rejectReassignRequest` | Reject a reassignment request |