@fias/create-fias-plugin 1.22.0 → 1.24.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/package.json +1 -1
- package/templates/default/AGENTS.md +142 -31
- package/templates/default/CLAUDE.md +142 -31
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- fias-sdk-guide-version: 2.
|
|
1
|
+
<!-- fias-sdk-guide-version: 2.32.0 -->
|
|
2
2
|
|
|
3
3
|
# FIAS Plugin Development Guide
|
|
4
4
|
|
|
@@ -173,9 +173,13 @@ pairings instead of the flat catalog, see `FONT_PAIRINGS`.
|
|
|
173
173
|
import { useFiasUser } from '@fias/arche-sdk';
|
|
174
174
|
|
|
175
175
|
const user = useFiasUser();
|
|
176
|
-
// {
|
|
176
|
+
// { ref: string, displayName: string, avatar: string | null }
|
|
177
177
|
```
|
|
178
178
|
|
|
179
|
+
`ref` is your app's identifier for this person (`aur_…`): the same everywhere in your app, different
|
|
180
|
+
in every other app. Store it wherever your data names a person. Apps that existed before refs also
|
|
181
|
+
get a deprecated `userId`; new apps never do. In the dev harness, Mock mode gives the mock user a stable ref, and Live mode the ref the platform allocated for you; set `"mockUser": { "legacyUserId": true }` in `fias-dev.config.json` to also get `userId`, as an app from before refs does.
|
|
182
|
+
|
|
179
183
|
### `useFiasHolder()` — The Fias workspace the app was opened in
|
|
180
184
|
|
|
181
185
|
**Permission:** `user:profile:read` or `data:workspace`
|
|
@@ -336,6 +340,7 @@ A collection can carry rules the platform checks on **every** write, so a buggy
|
|
|
336
340
|
- **`nonNegative`**: these numeric fields must be ≥ 0 when present.
|
|
337
341
|
- **`fieldBounds`**: `field <op> bound`, where `op` is `eq` / `ne` / `lt` / `lte` / `gt` / `gte` and the bound is a constant `value` or a sibling field `valueField` (exactly one).
|
|
338
342
|
- **`immutableFields`**: set once; an overwrite must carry the same value.
|
|
343
|
+
- **`personFields`**: these fields name people. Each holds one app user ref (`useFiasUser().ref`) or a list of up to 20, never a name, username or id. Declare every field your data uses for a person: it is what lets Fias relate records to people and carry refs safely between apps.
|
|
339
344
|
- **`references`**: `field` holds the key of a document that must exist in `collection`, in the same scope (same workspace, same user, or shared). An absent or null field is allowed. The writer must be able to read the target collection. Deleting a document that something still references is refused; there is no cascade.
|
|
340
345
|
- **`ledgerSums`**: a counter `field` may change only by exactly the sum of the rows appended to `ledger` in the **same write**. A ledger row counts toward the counter whose key its term's `key` field holds, adding its `amount` (subtracted with `negate: true`, filtered with `when: { field, in: [...] }`). A counter that moves without rows, and rows whose counter did not move by their sum, are both refused. The ledger must be `appendOnly` and have the counter's scope; an absent counter counts as 0. At most 4 terms per rule.
|
|
341
346
|
|
|
@@ -599,7 +604,8 @@ function Team() {
|
|
|
599
604
|
|
|
600
605
|
async function setup() {
|
|
601
606
|
const ws = await workspaces.create('Acme Corp'); // caller becomes owner
|
|
602
|
-
|
|
607
|
+
// Fias shows the person a sheet; they choose who to add. You get refs back.
|
|
608
|
+
const { added } = await workspaces.addPeople(ws.workspaceId, { role: 'member' });
|
|
603
609
|
|
|
604
610
|
// Workspace-scoped collection + document (role-gated server-side).
|
|
605
611
|
await dataStore.createCollection('ledger', { userScope: 'workspace' });
|
|
@@ -613,7 +619,8 @@ function Team() {
|
|
|
613
619
|
);
|
|
614
620
|
|
|
615
621
|
const mine = await workspaces.list(); // workspaces I belong to
|
|
616
|
-
const members = await workspaces.listMembers(ws.workspaceId);
|
|
622
|
+
const members = await workspaces.listMembers(ws.workspaceId); // [{ ref, displayName, role, … }]
|
|
623
|
+
await workspaces.updateMember(ws.workspaceId, { ref: members[1].ref }, 'admin');
|
|
617
624
|
}
|
|
618
625
|
}
|
|
619
626
|
```
|
|
@@ -628,7 +635,14 @@ if (holder?.type === 'org') {
|
|
|
628
635
|
}
|
|
629
636
|
```
|
|
630
637
|
|
|
631
|
-
**
|
|
638
|
+
**Naming people.** Store `useFiasUser().ref` wherever your data names a person (an assignee, an "uploaded by"), and turn refs back into names with `resolvePeople(refs, workspaceId)`: up to 100 at a time, in order, each `{ ref, displayName, avatar }` or `null`. A ref is named only while it belongs to a current member of that workspace — someone who left, an unknown ref or another app's ref is `null`, and you can't tell which. Without `workspaceId` only the user's own ref resolves. In a Fias org workspace, names also need the `data:workspace-people:read` permission (human-reviewed) **and** the workspace's approval of your app covering it; without both every ref is `null`, so show a neutral label. Names never come with an id or an email.
|
|
639
|
+
|
|
640
|
+
```tsx
|
|
641
|
+
const [assignee] = await workspaces.resolvePeople([task.assigneeRef], shared.workspaceId);
|
|
642
|
+
const label = assignee?.displayName ?? 'Former member';
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
**Methods:** `create`, `openForWorkspace`, `list`, `get`, `archive` (owner only — final: an archived workspace cannot be reopened, and it is deleted with its documents 30 days later; say so before your user archives), `listMembers`, `addPeople`, `updateMember`, `removeMember`, `resolvePeople`. **Add people with `addPeople(workspaceId, { role? })`**: Fias draws a sheet in which the person picks co-members of their Fias workspace or types an exact username, and may change the role (you suggest `'admin'`, `'member'` or `'viewer'`); it resolves with `{ added: [{ ref, displayName, role }] }` — empty if they added no one. Your code never names who to add. (`addMember(workspaceId, username, …)` is deprecated: it works only for apps from before app user refs, and in the dev harness and Forge test channel; elsewhere it rejects with `ADD_MEMBER_USE_SHEET`.) Members come back named by your app's `ref` for them and their `displayName` — never a username or id — and `updateMember` / `removeMember` take that `{ ref }`. **Rate limits (per minute):** `create`/`archive` 20, member ops 30, `list`/`get`/`listMembers`/`resolvePeople` 120. **Error codes:** `NOT_A_MEMBER`, `FORBIDDEN`, `ROLE_CEILING`, `ADD_PEOPLE_UNAVAILABLE` (no sheet here: the Forge test channel, Company portals), `MEMBER_TARGET_USE_REF` (name members by `{ ref }`), `SHEET_BUSY`, `MUST_KEEP_ONE_OWNER`, `WORKSPACE_NOT_FOUND`, `HOLDER_BACKED_WORKSPACE`, `HOLDER_NOT_ELIGIBLE`. Read workspace documents with `useFiasDataStore().get`/`query`/`search` passing `{ workspaceId }` (every active member can read; make the collection `searchable` to use `search`). **Per-collection role floors:** raise a workspace collection's minimum role at creation — `createCollection('board_minutes', { userScope: 'workspace', writeMinRole: 'admin', readMinRole: 'member' })` makes that collection admin-write / member-read, above the default (write ≥ member, read ≥ viewer).
|
|
632
646
|
|
|
633
647
|
### `useFiasAccess()` — The current user's own access in a workspace
|
|
634
648
|
|
|
@@ -1697,7 +1711,7 @@ await library.create({
|
|
|
1697
1711
|
await library.update(style.itemId, { words: 'brighter', expectedVersion: style.version });
|
|
1698
1712
|
```
|
|
1699
1713
|
|
|
1700
|
-
The Creative Library holds the user's **styles** (a look: words and 1–
|
|
1714
|
+
The Creative Library holds the user's **styles** (a look: words and 1–100 reference pictures — a request sends up to three, the ones you choose with `pictures`) and **characters** (one reference picture, an appearance, and a style). They belong to the user — or, in a team workspace that approved your arche, to the workspace — not to the arche that made them: FiasWorks Studio makes most of them, and any arche the user allows can use them. Reading them is seeing their pictures, so the permission is disclosed that way. Every change writes a new version (`get(id, version)` reads an old one); pass `expectedVersion` to `update` so a change made elsewhere is never overwritten. An update may keep pictures the item already has, whoever added them; only pictures you add must be your arche's own. Keep your own fields in `extension` — you see and change only yours. In a workspace, viewers can read but not change (`WORKSPACE_VIEWER_READ_ONLY`). `pick()` shows exactly what `list()` would return, so prefer it to drawing your own chooser; it rejects with `PICKER_UNAVAILABLE` in the dev harness (use `list()` there) and `SHEET_BUSY` while another Fias sheet is open. Not available in the builder preview or the test channel. **Error codes:** `CREATIVE_ITEM_NOT_FOUND`, `CREATIVE_REFERENCE_NOT_USABLE`, `CREATIVE_STYLE_REF_INVALID`, `CREATIVE_VERSION_CONFLICT`, `CREATIVE_LIBRARY_FULL`, `CREATIVE_INVALID`; on `generate`, `STYLE_NOT_FOUND`, `STYLE_NEEDS_DESCRIPTION` and `STYLE_PICTURE_NOT_IN_STYLE`.
|
|
1701
1715
|
|
|
1702
1716
|
### `useCreateAsset()` — Game art the FiasWorks way
|
|
1703
1717
|
|
|
@@ -1785,6 +1799,45 @@ await connections.release(connectionId); // give up your own access (owners and
|
|
|
1785
1799
|
|
|
1786
1800
|
A person connects an outside account (X: `'x'`, LinkedIn: `'linkedin'`) to a Fias workspace — their personal one, or an organization's, where only owners and admins can — and lets your app use it. Your app **never sees a credential**: `connect(provider)` opens a screen the platform draws (it offers the workspace's existing accounts for that provider, or connects a new one in a popup) and resolves with the connection the person chose, or `{ canceled: true }`. `list()` returns only the accounts the workspace the app is open in lets it use; one with `status: 'needs_reconnect'` needs the person to connect it again. People manage connections and every app's access in Workspace Controls. `release` gives up the app's access for the whole workspace, so only the workspace's owners and admins may call it (`ROLE_NOT_ALLOWED` otherwise). Not available in the builder preview or the test channel. **Error codes:** `CONNECTION_NOT_FOUND`, `PERMISSION_DENIED`, `CONNECT_UNAVAILABLE`, `ROLE_NOT_ALLOWED`.
|
|
1787
1801
|
|
|
1802
|
+
### `useFiasWebSources()` — Read pages on Fias-approved websites, as data
|
|
1803
|
+
|
|
1804
|
+
**Permission:** `web:sources:read` (human-reviewed at publish)
|
|
1805
|
+
**Returns:** `FiasWebSourcesApi`
|
|
1806
|
+
|
|
1807
|
+
```tsx
|
|
1808
|
+
import { useFiasWebSources } from '@fias/arche-sdk';
|
|
1809
|
+
|
|
1810
|
+
const web = useFiasWebSources();
|
|
1811
|
+
const { sites } = await web.listSites({ category: 'credit_union', search: 'becu' });
|
|
1812
|
+
// [{ host: 'www.becu.org', name: 'BECU', category: 'credit_union' }]
|
|
1813
|
+
const result = await web.extract({
|
|
1814
|
+
url: 'https://www.becu.org/everyday-banking/certificates',
|
|
1815
|
+
form: {
|
|
1816
|
+
description: 'Share certificate rates',
|
|
1817
|
+
fields: {
|
|
1818
|
+
asOf: { kind: 'date', description: 'Date the rates are effective, if stated' },
|
|
1819
|
+
rates: {
|
|
1820
|
+
kind: 'list',
|
|
1821
|
+
maxItems: 50,
|
|
1822
|
+
item: {
|
|
1823
|
+
product: { kind: 'enum', values: ['certificate', 'ira_certificate', 'other'] },
|
|
1824
|
+
months: { kind: 'integer', min: 1, max: 120, description: 'Term in months' },
|
|
1825
|
+
apy: { kind: 'number', min: 0, max: 25, description: 'APY in percent' },
|
|
1826
|
+
},
|
|
1827
|
+
},
|
|
1828
|
+
},
|
|
1829
|
+
},
|
|
1830
|
+
maxAgeSeconds: 24 * 3600,
|
|
1831
|
+
});
|
|
1832
|
+
// result.data.rates, result.site.name, result.fetchedAt, result.fromCache, result.discarded
|
|
1833
|
+
```
|
|
1834
|
+
|
|
1835
|
+
Your app reads pages only on **websites Fias approved** (regulated institutions such as credit unions and banks, and government sites) — any page on an approved site, so a redesigned or moved page keeps working. To have a site approved, ask Fias. `listSites()` lists them (free).
|
|
1836
|
+
|
|
1837
|
+
`extract()` never returns the page's text — not even a short label. You send a **form** — up to 30 fields of kinds `number` and `integer` (optional `min`/`max`), `boolean`, `date` (`YYYY-MM-DD`), `enum` (your own `values`) and `list` (up to 200 rows of those) — and Fias reads the page into it. There is no free-text kind: describe what you need as numbers, dates, yes/no and choices you list (a product type, not a product name). Every value may be `null` (the page did not say); a value that broke its field's rules comes back `null` and is named in `discarded`. URLs must be `https://`; `redirected: true` means the site moved the page within itself (the new address is not returned). The reader fills the form from whatever layout the page has, but it never runs JavaScript: a page that builds its figures with scripts comes back with `scriptHeavy: true` and mostly nulls — read the site's PDF rate sheet instead.
|
|
1838
|
+
|
|
1839
|
+
One copy of each page serves every app, and a page is fetched from its site **at most once an hour**. `maxAgeSeconds` (default one day, 3600–30 days) is the oldest copy you accept. A fresh fetch (about a tenth of a cent) and the reading (a fast model's tokens — well under a cent for a typical page) are charged to the user; a cached result costs nothing (`fromCache: true`). `stale: true` means the site could not be asked again just now and you got the last copy. Always show `site.name` and `fetchedAt` beside the figures. Not available in the builder preview or the test channel. **Error codes:** `WEB_SOURCES_DISABLED`, `INVALID_URL`, `SITE_NOT_APPROVED`, `PAGE_EXCLUDED` (a part of the site where the public posts), `ROBOTS_DISALLOWED`, `FETCH_FAILED`, `RESPONSE_TOO_LARGE`, `CONTENT_TYPE_NOT_ALLOWED`, `EXTRACTION_FAILED`, `SOURCE_BUSY`, `RATE_LIMIT` (surface it, never retry in a loop), `INSUFFICIENT_CREDITS`, `PERMISSION_DENIED`.
|
|
1840
|
+
|
|
1788
1841
|
### `useFiasX()` / `useFiasLinkedIn()` — Act as a connected account
|
|
1789
1842
|
|
|
1790
1843
|
**Permission:** `connections:use` (human-reviewed at publish)
|
|
@@ -1844,7 +1897,7 @@ import { useCompanyEmail } from '@fias/arche-sdk';
|
|
|
1844
1897
|
const email = useCompanyEmail();
|
|
1845
1898
|
const { results, sent } = await email.send({
|
|
1846
1899
|
requestKey: `digest-${weekId}`, // reuse on retry — never double-sends
|
|
1847
|
-
to:
|
|
1900
|
+
to: assigneeRefs.map((ref) => ({ ref })), // refs your app stored (useFiasUser().ref)
|
|
1848
1901
|
subject: 'Your weekly digest',
|
|
1849
1902
|
text: digestText, // required
|
|
1850
1903
|
html: digestHtml, // optional, sanitized
|
|
@@ -1853,7 +1906,7 @@ const { results, sent } = await email.send({
|
|
|
1853
1906
|
```
|
|
1854
1907
|
|
|
1855
1908
|
- **You never choose the From address** — it is the address the Company assigned your app. You choose recipients and content.
|
|
1856
|
-
- **Name members by `
|
|
1909
|
+
- **Name members by their ref** (`{ ref }` — see Naming people under `useFiasWorkspaces()`); you never see their addresses. A ref that names no one is refused exactly like a member outside what the Company allowed. `{ userId }` is deprecated and accepted only from apps that existed before refs. Raw `{ email }` recipients work only if the Company let your app email anyone. One recipient outside what the Company allowed refuses the whole call (without saying which).
|
|
1857
1910
|
- **The user must be working in one of the Company's workspaces, with edit access.** Each recipient is charged to that user, like any paid call; up to 50 recipients per call, and a daily limit per app per Company.
|
|
1858
1911
|
- **Retry with the same `requestKey`.** A recipient already sent under that key is not sent again. `unknown` means it may have gone out — do not resend it under a new key.
|
|
1859
1912
|
- **HTML is sanitized** to the email allow-list: tables, inline presentational styles, `https` images and `https`/`mailto` links are kept; `<script>`, `<form>`, `<iframe>` and similar refuse the send (`EMAIL_CONTENT_REJECTED`).
|
|
@@ -1985,6 +2038,33 @@ A host older than this option ignores it and pushes. If you correct paths
|
|
|
1985
2038
|
automatically as they arrive, never repeat the same correction twice in a
|
|
1986
2039
|
row, or a push-only host can trap the Back button between two URLs.
|
|
1987
2040
|
|
|
2041
|
+
### `useOpener()` — Where the person came from, and "Use in" there
|
|
2042
|
+
|
|
2043
|
+
**Permission:** None to read it or go back; `opener:send` for `send()`
|
|
2044
|
+
|
|
2045
|
+
When a Fias tool opens your arche — the forge page's or the Arche Builder's "Open FiasWorks Studio" — Fias tells you where the person came from, and lets you send them back and hand one of your files over.
|
|
2046
|
+
|
|
2047
|
+
```tsx
|
|
2048
|
+
import { useOpener } from '@fias/arche-sdk';
|
|
2049
|
+
|
|
2050
|
+
const { opener, returnTo, send } = useOpener();
|
|
2051
|
+
// opener: { kind: 'forge' | 'builder', name: 'My Arche' } — or null
|
|
2052
|
+
|
|
2053
|
+
{
|
|
2054
|
+
opener && <button onClick={returnTo}>Return to {opener.name}</button>;
|
|
2055
|
+
}
|
|
2056
|
+
{
|
|
2057
|
+
opener && (
|
|
2058
|
+
<button onClick={() => send({ fileId: picture.cutoutFileId, name: 'pumpkin.png' })}>
|
|
2059
|
+
Use in {opener.name}
|
|
2060
|
+
</button>
|
|
2061
|
+
);
|
|
2062
|
+
}
|
|
2063
|
+
// send resolves { sent: true, name } (the name it landed as) or { canceled: true }
|
|
2064
|
+
```
|
|
2065
|
+
|
|
2066
|
+
`send` offers a Vault file **your arche created** — a picture you generated, cut out or edited. Fias shows the person a sheet naming the file and where it goes, and only their Send moves it: into a forge's `public/` (Claude Code is told it arrived), or an Arche Builder arche's asset library (which needs the arche published once). You never name the destination, and nothing you did not make can be sent. Show both buttons only when `opener` is set; it is null when your arche was opened any other way, and in the dev harness. **Error codes:** `NO_OPENER`, `SHEET_BUSY`, `SEND_UNAVAILABLE`.
|
|
2067
|
+
|
|
1988
2068
|
### `useFiasChrome()` — Expand, contract and the host chrome
|
|
1989
2069
|
|
|
1990
2070
|
**Permission:** None required
|
|
@@ -2254,28 +2334,30 @@ The SDK also exports the following for advanced use cases. Most plugins don't ne
|
|
|
2254
2334
|
|
|
2255
2335
|
**Fields:**
|
|
2256
2336
|
|
|
2257
|
-
| Field | Req
|
|
2258
|
-
| --------------------- |
|
|
2259
|
-
| `name` | Yes
|
|
2260
|
-
| `version` | Yes
|
|
2261
|
-
| `description` | Yes
|
|
2262
|
-
| `expandedDescription` | No
|
|
2263
|
-
| `main` | Yes
|
|
2264
|
-
| `archeType` | Yes
|
|
2265
|
-
| `categorySlug` | No
|
|
2266
|
-
| `icon` | No
|
|
2267
|
-
| `tags` | No
|
|
2268
|
-
| `pricing` | Yes
|
|
2269
|
-
| `permissions` | Yes
|
|
2270
|
-
| `products` | No
|
|
2271
|
-
| `isPublic` | No
|
|
2272
|
-
| `isFullScreen` | No
|
|
2273
|
-
| `showSidebar` | No
|
|
2274
|
-
| `showExpandButton` | No
|
|
2275
|
-
| `isListed` | No
|
|
2276
|
-
| `
|
|
2277
|
-
| `
|
|
2278
|
-
| `
|
|
2337
|
+
| Field | Req | Description |
|
|
2338
|
+
| --------------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
2339
|
+
| `name` | Yes | Plugin identifier (lowercase, hyphens) |
|
|
2340
|
+
| `version` | Yes | Semver (e.g., `"1.0.0"`) |
|
|
2341
|
+
| `description` | Yes | Short marketplace description |
|
|
2342
|
+
| `expandedDescription` | No | Long-form marketplace copy |
|
|
2343
|
+
| `main` | Yes | Entry point source file |
|
|
2344
|
+
| `archeType` | Yes | `"tool"` or `"site"` |
|
|
2345
|
+
| `categorySlug` | No | Marketplace category for discovery |
|
|
2346
|
+
| `icon` | No | Path to an icon image **under `public/`** (e.g. `public/icon.png`) — only `src/` and `public/` are packaged. Applied on every publish (local, staging and prod) and rehosted as your marketplace/store icon. PNG, JPEG, WebP or SVG, ≤5 MB; raster images must also be ≤1024×1024 px (SVG has no pixel limit). An invalid icon rejects the submission: a first-publish listing fee is refunded, but the AI review that already ran is still billed. To change it, replace the file (or the path) and republish; removing the field keeps the current icon. |
|
|
2347
|
+
| `tags` | No | Discovery tags |
|
|
2348
|
+
| `pricing` | Yes | Marketplace **listing** pricing (see "Pricing detail" below). NOT for in-arche charges — use `products` |
|
|
2349
|
+
| `permissions` | Yes | Array of permission scopes (see below) |
|
|
2350
|
+
| `products` | No | IAP product catalog for `useFiasStore()` (see "Defining IAP products" below) |
|
|
2351
|
+
| `isPublic` | No | Default `false`. Anonymous visitors can view (see "Public arches" below) |
|
|
2352
|
+
| `isFullScreen` | No | Default `false`. Renders without platform chrome — implies `archeType: "site"` |
|
|
2353
|
+
| `showSidebar` | No | Default `true`. Set `false` to keep the platform top nav and drop the suggested-arches sidebar (see "Declining the sidebar" below) |
|
|
2354
|
+
| `showExpandButton` | No | Default `true`. Set `false` if you want neither the host's floating expand button nor your own (see "Controlling the host chrome" below) |
|
|
2355
|
+
| `isListed` | No | Default `true`. Visible in marketplace store |
|
|
2356
|
+
| `isGame` | No | Default `false`. `true` lists your arche on the Games page (`/a/games`) and badges it as a game in the Arche Store. Requires `game`. Omitting it on a republish takes the arche out of Games |
|
|
2357
|
+
| `game` | With `isGame` | `{ "genres": [...] }` — 1-4 (more only where a Fias admin allowed your arche) of `action`, `rpg`, `board`, `cards`, `casual`, `crafting`, `flying`, `match`, `platform`, `puzzle`, `strategy`, `word`, `racing`, `sports`, `simulation`, `trivia`, `music`, `educational`, `cozy`, `idle`, `tower-defense` (a kind of `strategy`: its games are listed there too). Refused without `isGame: true` |
|
|
2358
|
+
| `sdk` | Yes | SDK version range |
|
|
2359
|
+
| `dependencies` | No | npm packages with **exact** versions (max 20) |
|
|
2360
|
+
| `archeIds` | Auto | Per-environment IDs written by the CLI after first publish — don't hand-edit |
|
|
2279
2361
|
|
|
2280
2362
|
**Pricing detail:**
|
|
2281
2363
|
|
|
@@ -2290,7 +2372,7 @@ The SDK also exports the following for advanced use cases. Most plugins don't ne
|
|
|
2290
2372
|
|
|
2291
2373
|
Other optional sub-fields: `currency: "usd"` (only USD supported today), `platformFeePercent` (override the default platform cut — non-standard).
|
|
2292
2374
|
|
|
2293
|
-
**Permissions:** `theme:read`, `user:profile:read`, `storage:sandbox`, `storage:local`, `data:store`, `data:search`, `data:workspace`, `entities:invoke`, `entities:client_invoke`, `entities:image_generate`, `entities:image_edit`, `entities:audio_generate`, `entities:web_search`, `store:purchase`, `vault:documents:read`, `vault:documents:write`, `vault:user-documents:read`, `vault:user-documents:write`, `vault:user-documents:edit`, `vault:workspace-documents:read`, `vault:creative:read`, `vault:creative:write`, `assets:read`, `assets:community:read`, `assets:community:publish`, `litcraft:hub`, `navigation:open_arche`, `sandbox:vendored-libraries`, `ai:actions`
|
|
2375
|
+
**Permissions:** `theme:read`, `user:profile:read`, `storage:sandbox`, `storage:local`, `data:store`, `data:search`, `data:workspace`, `entities:invoke`, `entities:client_invoke`, `entities:image_generate`, `entities:image_edit`, `entities:audio_generate`, `entities:web_search`, `store:purchase`, `vault:documents:read`, `vault:documents:write`, `vault:user-documents:read`, `vault:user-documents:write`, `vault:user-documents:edit`, `vault:workspace-documents:read`, `vault:creative:read`, `vault:creative:write`, `opener:send`, `assets:read`, `assets:community:read`, `assets:community:publish`, `litcraft:hub`, `navigation:open_arche`, `sandbox:vendored-libraries`, `ai:actions`, `connections:use`, `schedule:invoke`, `web:sources:read`
|
|
2294
2376
|
|
|
2295
2377
|
**Using AI:** Add `"entities:invoke"` to permissions, then use `useEntityInvocation()` with a model entity ID (or a `{ capability }` selector, or an entry from `useTextModels()`) and your own `systemPrompt`. Browse available models with `npx fias-dev entities`.
|
|
2296
2378
|
|
|
@@ -2513,6 +2595,35 @@ npx fias-dev collaborators remove <identifier> # Revoke a collaborat
|
|
|
2513
2595
|
|
|
2514
2596
|
All commands accept `--env <prod|staging|local>` (default `prod`).
|
|
2515
2597
|
|
|
2598
|
+
### Extra Store listings (games inside your arche)
|
|
2599
|
+
|
|
2600
|
+
If your arche holds several games (or pages) that people should find on their own — Flappyland's mini-games, each at its own path — give each one an **extra Store listing**: its own title, description, square icon (also its iOS Home Screen icon) and genres, opening at its path. They show in Store search, on your Store page and on the Games page. Each listing uses a slot: **$50 once** (or a free listing an admin granted you); retiring one frees its slot for another, never refunds it. Your app must handle the path (`useFiasNavigation().currentPath`).
|
|
2601
|
+
|
|
2602
|
+
Manage them on your arche's settings page ("Store listings"), or from a `store-listings.json` file:
|
|
2603
|
+
|
|
2604
|
+
```json
|
|
2605
|
+
{
|
|
2606
|
+
"listings": [
|
|
2607
|
+
{
|
|
2608
|
+
"path": "/games/wordtrace",
|
|
2609
|
+
"title": "Wordtrace",
|
|
2610
|
+
"description": "Trace as many words as you can through a grid of letters.",
|
|
2611
|
+
"isGame": true,
|
|
2612
|
+
"genres": ["word"],
|
|
2613
|
+
"icon": "store-art/wordtrace.png"
|
|
2614
|
+
}
|
|
2615
|
+
]
|
|
2616
|
+
}
|
|
2617
|
+
```
|
|
2618
|
+
|
|
2619
|
+
```bash
|
|
2620
|
+
npx fias-dev listings list --env staging # Slots, live and retired listings
|
|
2621
|
+
npx fias-dev listings push --env staging # Show the plan and the charge, then ask
|
|
2622
|
+
npx fias-dev listings push --env staging --dry-run # Plan only
|
|
2623
|
+
```
|
|
2624
|
+
|
|
2625
|
+
Icons: square PNG, JPEG or WebP, 512×512 recommended (256–1024), up to 512 KB — keep them outside `public/` so they don't ship in your bundle. Every listing passes a content check (about 1 credit) before anything is charged; a refused one costs nothing else.
|
|
2626
|
+
|
|
2516
2627
|
### Other CLI diagnostics
|
|
2517
2628
|
|
|
2518
2629
|
```bash
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- fias-sdk-guide-version: 2.
|
|
1
|
+
<!-- fias-sdk-guide-version: 2.32.0 -->
|
|
2
2
|
|
|
3
3
|
# FIAS Plugin Development Guide
|
|
4
4
|
|
|
@@ -173,9 +173,13 @@ pairings instead of the flat catalog, see `FONT_PAIRINGS`.
|
|
|
173
173
|
import { useFiasUser } from '@fias/arche-sdk';
|
|
174
174
|
|
|
175
175
|
const user = useFiasUser();
|
|
176
|
-
// {
|
|
176
|
+
// { ref: string, displayName: string, avatar: string | null }
|
|
177
177
|
```
|
|
178
178
|
|
|
179
|
+
`ref` is your app's identifier for this person (`aur_…`): the same everywhere in your app, different
|
|
180
|
+
in every other app. Store it wherever your data names a person. Apps that existed before refs also
|
|
181
|
+
get a deprecated `userId`; new apps never do. In the dev harness, Mock mode gives the mock user a stable ref, and Live mode the ref the platform allocated for you; set `"mockUser": { "legacyUserId": true }` in `fias-dev.config.json` to also get `userId`, as an app from before refs does.
|
|
182
|
+
|
|
179
183
|
### `useFiasHolder()` — The Fias workspace the app was opened in
|
|
180
184
|
|
|
181
185
|
**Permission:** `user:profile:read` or `data:workspace`
|
|
@@ -336,6 +340,7 @@ A collection can carry rules the platform checks on **every** write, so a buggy
|
|
|
336
340
|
- **`nonNegative`**: these numeric fields must be ≥ 0 when present.
|
|
337
341
|
- **`fieldBounds`**: `field <op> bound`, where `op` is `eq` / `ne` / `lt` / `lte` / `gt` / `gte` and the bound is a constant `value` or a sibling field `valueField` (exactly one).
|
|
338
342
|
- **`immutableFields`**: set once; an overwrite must carry the same value.
|
|
343
|
+
- **`personFields`**: these fields name people. Each holds one app user ref (`useFiasUser().ref`) or a list of up to 20, never a name, username or id. Declare every field your data uses for a person: it is what lets Fias relate records to people and carry refs safely between apps.
|
|
339
344
|
- **`references`**: `field` holds the key of a document that must exist in `collection`, in the same scope (same workspace, same user, or shared). An absent or null field is allowed. The writer must be able to read the target collection. Deleting a document that something still references is refused; there is no cascade.
|
|
340
345
|
- **`ledgerSums`**: a counter `field` may change only by exactly the sum of the rows appended to `ledger` in the **same write**. A ledger row counts toward the counter whose key its term's `key` field holds, adding its `amount` (subtracted with `negate: true`, filtered with `when: { field, in: [...] }`). A counter that moves without rows, and rows whose counter did not move by their sum, are both refused. The ledger must be `appendOnly` and have the counter's scope; an absent counter counts as 0. At most 4 terms per rule.
|
|
341
346
|
|
|
@@ -599,7 +604,8 @@ function Team() {
|
|
|
599
604
|
|
|
600
605
|
async function setup() {
|
|
601
606
|
const ws = await workspaces.create('Acme Corp'); // caller becomes owner
|
|
602
|
-
|
|
607
|
+
// Fias shows the person a sheet; they choose who to add. You get refs back.
|
|
608
|
+
const { added } = await workspaces.addPeople(ws.workspaceId, { role: 'member' });
|
|
603
609
|
|
|
604
610
|
// Workspace-scoped collection + document (role-gated server-side).
|
|
605
611
|
await dataStore.createCollection('ledger', { userScope: 'workspace' });
|
|
@@ -613,7 +619,8 @@ function Team() {
|
|
|
613
619
|
);
|
|
614
620
|
|
|
615
621
|
const mine = await workspaces.list(); // workspaces I belong to
|
|
616
|
-
const members = await workspaces.listMembers(ws.workspaceId);
|
|
622
|
+
const members = await workspaces.listMembers(ws.workspaceId); // [{ ref, displayName, role, … }]
|
|
623
|
+
await workspaces.updateMember(ws.workspaceId, { ref: members[1].ref }, 'admin');
|
|
617
624
|
}
|
|
618
625
|
}
|
|
619
626
|
```
|
|
@@ -628,7 +635,14 @@ if (holder?.type === 'org') {
|
|
|
628
635
|
}
|
|
629
636
|
```
|
|
630
637
|
|
|
631
|
-
**
|
|
638
|
+
**Naming people.** Store `useFiasUser().ref` wherever your data names a person (an assignee, an "uploaded by"), and turn refs back into names with `resolvePeople(refs, workspaceId)`: up to 100 at a time, in order, each `{ ref, displayName, avatar }` or `null`. A ref is named only while it belongs to a current member of that workspace — someone who left, an unknown ref or another app's ref is `null`, and you can't tell which. Without `workspaceId` only the user's own ref resolves. In a Fias org workspace, names also need the `data:workspace-people:read` permission (human-reviewed) **and** the workspace's approval of your app covering it; without both every ref is `null`, so show a neutral label. Names never come with an id or an email.
|
|
639
|
+
|
|
640
|
+
```tsx
|
|
641
|
+
const [assignee] = await workspaces.resolvePeople([task.assigneeRef], shared.workspaceId);
|
|
642
|
+
const label = assignee?.displayName ?? 'Former member';
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
**Methods:** `create`, `openForWorkspace`, `list`, `get`, `archive` (owner only — final: an archived workspace cannot be reopened, and it is deleted with its documents 30 days later; say so before your user archives), `listMembers`, `addPeople`, `updateMember`, `removeMember`, `resolvePeople`. **Add people with `addPeople(workspaceId, { role? })`**: Fias draws a sheet in which the person picks co-members of their Fias workspace or types an exact username, and may change the role (you suggest `'admin'`, `'member'` or `'viewer'`); it resolves with `{ added: [{ ref, displayName, role }] }` — empty if they added no one. Your code never names who to add. (`addMember(workspaceId, username, …)` is deprecated: it works only for apps from before app user refs, and in the dev harness and Forge test channel; elsewhere it rejects with `ADD_MEMBER_USE_SHEET`.) Members come back named by your app's `ref` for them and their `displayName` — never a username or id — and `updateMember` / `removeMember` take that `{ ref }`. **Rate limits (per minute):** `create`/`archive` 20, member ops 30, `list`/`get`/`listMembers`/`resolvePeople` 120. **Error codes:** `NOT_A_MEMBER`, `FORBIDDEN`, `ROLE_CEILING`, `ADD_PEOPLE_UNAVAILABLE` (no sheet here: the Forge test channel, Company portals), `MEMBER_TARGET_USE_REF` (name members by `{ ref }`), `SHEET_BUSY`, `MUST_KEEP_ONE_OWNER`, `WORKSPACE_NOT_FOUND`, `HOLDER_BACKED_WORKSPACE`, `HOLDER_NOT_ELIGIBLE`. Read workspace documents with `useFiasDataStore().get`/`query`/`search` passing `{ workspaceId }` (every active member can read; make the collection `searchable` to use `search`). **Per-collection role floors:** raise a workspace collection's minimum role at creation — `createCollection('board_minutes', { userScope: 'workspace', writeMinRole: 'admin', readMinRole: 'member' })` makes that collection admin-write / member-read, above the default (write ≥ member, read ≥ viewer).
|
|
632
646
|
|
|
633
647
|
### `useFiasAccess()` — The current user's own access in a workspace
|
|
634
648
|
|
|
@@ -1697,7 +1711,7 @@ await library.create({
|
|
|
1697
1711
|
await library.update(style.itemId, { words: 'brighter', expectedVersion: style.version });
|
|
1698
1712
|
```
|
|
1699
1713
|
|
|
1700
|
-
The Creative Library holds the user's **styles** (a look: words and 1–
|
|
1714
|
+
The Creative Library holds the user's **styles** (a look: words and 1–100 reference pictures — a request sends up to three, the ones you choose with `pictures`) and **characters** (one reference picture, an appearance, and a style). They belong to the user — or, in a team workspace that approved your arche, to the workspace — not to the arche that made them: FiasWorks Studio makes most of them, and any arche the user allows can use them. Reading them is seeing their pictures, so the permission is disclosed that way. Every change writes a new version (`get(id, version)` reads an old one); pass `expectedVersion` to `update` so a change made elsewhere is never overwritten. An update may keep pictures the item already has, whoever added them; only pictures you add must be your arche's own. Keep your own fields in `extension` — you see and change only yours. In a workspace, viewers can read but not change (`WORKSPACE_VIEWER_READ_ONLY`). `pick()` shows exactly what `list()` would return, so prefer it to drawing your own chooser; it rejects with `PICKER_UNAVAILABLE` in the dev harness (use `list()` there) and `SHEET_BUSY` while another Fias sheet is open. Not available in the builder preview or the test channel. **Error codes:** `CREATIVE_ITEM_NOT_FOUND`, `CREATIVE_REFERENCE_NOT_USABLE`, `CREATIVE_STYLE_REF_INVALID`, `CREATIVE_VERSION_CONFLICT`, `CREATIVE_LIBRARY_FULL`, `CREATIVE_INVALID`; on `generate`, `STYLE_NOT_FOUND`, `STYLE_NEEDS_DESCRIPTION` and `STYLE_PICTURE_NOT_IN_STYLE`.
|
|
1701
1715
|
|
|
1702
1716
|
### `useCreateAsset()` — Game art the FiasWorks way
|
|
1703
1717
|
|
|
@@ -1785,6 +1799,45 @@ await connections.release(connectionId); // give up your own access (owners and
|
|
|
1785
1799
|
|
|
1786
1800
|
A person connects an outside account (X: `'x'`, LinkedIn: `'linkedin'`) to a Fias workspace — their personal one, or an organization's, where only owners and admins can — and lets your app use it. Your app **never sees a credential**: `connect(provider)` opens a screen the platform draws (it offers the workspace's existing accounts for that provider, or connects a new one in a popup) and resolves with the connection the person chose, or `{ canceled: true }`. `list()` returns only the accounts the workspace the app is open in lets it use; one with `status: 'needs_reconnect'` needs the person to connect it again. People manage connections and every app's access in Workspace Controls. `release` gives up the app's access for the whole workspace, so only the workspace's owners and admins may call it (`ROLE_NOT_ALLOWED` otherwise). Not available in the builder preview or the test channel. **Error codes:** `CONNECTION_NOT_FOUND`, `PERMISSION_DENIED`, `CONNECT_UNAVAILABLE`, `ROLE_NOT_ALLOWED`.
|
|
1787
1801
|
|
|
1802
|
+
### `useFiasWebSources()` — Read pages on Fias-approved websites, as data
|
|
1803
|
+
|
|
1804
|
+
**Permission:** `web:sources:read` (human-reviewed at publish)
|
|
1805
|
+
**Returns:** `FiasWebSourcesApi`
|
|
1806
|
+
|
|
1807
|
+
```tsx
|
|
1808
|
+
import { useFiasWebSources } from '@fias/arche-sdk';
|
|
1809
|
+
|
|
1810
|
+
const web = useFiasWebSources();
|
|
1811
|
+
const { sites } = await web.listSites({ category: 'credit_union', search: 'becu' });
|
|
1812
|
+
// [{ host: 'www.becu.org', name: 'BECU', category: 'credit_union' }]
|
|
1813
|
+
const result = await web.extract({
|
|
1814
|
+
url: 'https://www.becu.org/everyday-banking/certificates',
|
|
1815
|
+
form: {
|
|
1816
|
+
description: 'Share certificate rates',
|
|
1817
|
+
fields: {
|
|
1818
|
+
asOf: { kind: 'date', description: 'Date the rates are effective, if stated' },
|
|
1819
|
+
rates: {
|
|
1820
|
+
kind: 'list',
|
|
1821
|
+
maxItems: 50,
|
|
1822
|
+
item: {
|
|
1823
|
+
product: { kind: 'enum', values: ['certificate', 'ira_certificate', 'other'] },
|
|
1824
|
+
months: { kind: 'integer', min: 1, max: 120, description: 'Term in months' },
|
|
1825
|
+
apy: { kind: 'number', min: 0, max: 25, description: 'APY in percent' },
|
|
1826
|
+
},
|
|
1827
|
+
},
|
|
1828
|
+
},
|
|
1829
|
+
},
|
|
1830
|
+
maxAgeSeconds: 24 * 3600,
|
|
1831
|
+
});
|
|
1832
|
+
// result.data.rates, result.site.name, result.fetchedAt, result.fromCache, result.discarded
|
|
1833
|
+
```
|
|
1834
|
+
|
|
1835
|
+
Your app reads pages only on **websites Fias approved** (regulated institutions such as credit unions and banks, and government sites) — any page on an approved site, so a redesigned or moved page keeps working. To have a site approved, ask Fias. `listSites()` lists them (free).
|
|
1836
|
+
|
|
1837
|
+
`extract()` never returns the page's text — not even a short label. You send a **form** — up to 30 fields of kinds `number` and `integer` (optional `min`/`max`), `boolean`, `date` (`YYYY-MM-DD`), `enum` (your own `values`) and `list` (up to 200 rows of those) — and Fias reads the page into it. There is no free-text kind: describe what you need as numbers, dates, yes/no and choices you list (a product type, not a product name). Every value may be `null` (the page did not say); a value that broke its field's rules comes back `null` and is named in `discarded`. URLs must be `https://`; `redirected: true` means the site moved the page within itself (the new address is not returned). The reader fills the form from whatever layout the page has, but it never runs JavaScript: a page that builds its figures with scripts comes back with `scriptHeavy: true` and mostly nulls — read the site's PDF rate sheet instead.
|
|
1838
|
+
|
|
1839
|
+
One copy of each page serves every app, and a page is fetched from its site **at most once an hour**. `maxAgeSeconds` (default one day, 3600–30 days) is the oldest copy you accept. A fresh fetch (about a tenth of a cent) and the reading (a fast model's tokens — well under a cent for a typical page) are charged to the user; a cached result costs nothing (`fromCache: true`). `stale: true` means the site could not be asked again just now and you got the last copy. Always show `site.name` and `fetchedAt` beside the figures. Not available in the builder preview or the test channel. **Error codes:** `WEB_SOURCES_DISABLED`, `INVALID_URL`, `SITE_NOT_APPROVED`, `PAGE_EXCLUDED` (a part of the site where the public posts), `ROBOTS_DISALLOWED`, `FETCH_FAILED`, `RESPONSE_TOO_LARGE`, `CONTENT_TYPE_NOT_ALLOWED`, `EXTRACTION_FAILED`, `SOURCE_BUSY`, `RATE_LIMIT` (surface it, never retry in a loop), `INSUFFICIENT_CREDITS`, `PERMISSION_DENIED`.
|
|
1840
|
+
|
|
1788
1841
|
### `useFiasX()` / `useFiasLinkedIn()` — Act as a connected account
|
|
1789
1842
|
|
|
1790
1843
|
**Permission:** `connections:use` (human-reviewed at publish)
|
|
@@ -1844,7 +1897,7 @@ import { useCompanyEmail } from '@fias/arche-sdk';
|
|
|
1844
1897
|
const email = useCompanyEmail();
|
|
1845
1898
|
const { results, sent } = await email.send({
|
|
1846
1899
|
requestKey: `digest-${weekId}`, // reuse on retry — never double-sends
|
|
1847
|
-
to:
|
|
1900
|
+
to: assigneeRefs.map((ref) => ({ ref })), // refs your app stored (useFiasUser().ref)
|
|
1848
1901
|
subject: 'Your weekly digest',
|
|
1849
1902
|
text: digestText, // required
|
|
1850
1903
|
html: digestHtml, // optional, sanitized
|
|
@@ -1853,7 +1906,7 @@ const { results, sent } = await email.send({
|
|
|
1853
1906
|
```
|
|
1854
1907
|
|
|
1855
1908
|
- **You never choose the From address** — it is the address the Company assigned your app. You choose recipients and content.
|
|
1856
|
-
- **Name members by `
|
|
1909
|
+
- **Name members by their ref** (`{ ref }` — see Naming people under `useFiasWorkspaces()`); you never see their addresses. A ref that names no one is refused exactly like a member outside what the Company allowed. `{ userId }` is deprecated and accepted only from apps that existed before refs. Raw `{ email }` recipients work only if the Company let your app email anyone. One recipient outside what the Company allowed refuses the whole call (without saying which).
|
|
1857
1910
|
- **The user must be working in one of the Company's workspaces, with edit access.** Each recipient is charged to that user, like any paid call; up to 50 recipients per call, and a daily limit per app per Company.
|
|
1858
1911
|
- **Retry with the same `requestKey`.** A recipient already sent under that key is not sent again. `unknown` means it may have gone out — do not resend it under a new key.
|
|
1859
1912
|
- **HTML is sanitized** to the email allow-list: tables, inline presentational styles, `https` images and `https`/`mailto` links are kept; `<script>`, `<form>`, `<iframe>` and similar refuse the send (`EMAIL_CONTENT_REJECTED`).
|
|
@@ -1985,6 +2038,33 @@ A host older than this option ignores it and pushes. If you correct paths
|
|
|
1985
2038
|
automatically as they arrive, never repeat the same correction twice in a
|
|
1986
2039
|
row, or a push-only host can trap the Back button between two URLs.
|
|
1987
2040
|
|
|
2041
|
+
### `useOpener()` — Where the person came from, and "Use in" there
|
|
2042
|
+
|
|
2043
|
+
**Permission:** None to read it or go back; `opener:send` for `send()`
|
|
2044
|
+
|
|
2045
|
+
When a Fias tool opens your arche — the forge page's or the Arche Builder's "Open FiasWorks Studio" — Fias tells you where the person came from, and lets you send them back and hand one of your files over.
|
|
2046
|
+
|
|
2047
|
+
```tsx
|
|
2048
|
+
import { useOpener } from '@fias/arche-sdk';
|
|
2049
|
+
|
|
2050
|
+
const { opener, returnTo, send } = useOpener();
|
|
2051
|
+
// opener: { kind: 'forge' | 'builder', name: 'My Arche' } — or null
|
|
2052
|
+
|
|
2053
|
+
{
|
|
2054
|
+
opener && <button onClick={returnTo}>Return to {opener.name}</button>;
|
|
2055
|
+
}
|
|
2056
|
+
{
|
|
2057
|
+
opener && (
|
|
2058
|
+
<button onClick={() => send({ fileId: picture.cutoutFileId, name: 'pumpkin.png' })}>
|
|
2059
|
+
Use in {opener.name}
|
|
2060
|
+
</button>
|
|
2061
|
+
);
|
|
2062
|
+
}
|
|
2063
|
+
// send resolves { sent: true, name } (the name it landed as) or { canceled: true }
|
|
2064
|
+
```
|
|
2065
|
+
|
|
2066
|
+
`send` offers a Vault file **your arche created** — a picture you generated, cut out or edited. Fias shows the person a sheet naming the file and where it goes, and only their Send moves it: into a forge's `public/` (Claude Code is told it arrived), or an Arche Builder arche's asset library (which needs the arche published once). You never name the destination, and nothing you did not make can be sent. Show both buttons only when `opener` is set; it is null when your arche was opened any other way, and in the dev harness. **Error codes:** `NO_OPENER`, `SHEET_BUSY`, `SEND_UNAVAILABLE`.
|
|
2067
|
+
|
|
1988
2068
|
### `useFiasChrome()` — Expand, contract and the host chrome
|
|
1989
2069
|
|
|
1990
2070
|
**Permission:** None required
|
|
@@ -2254,28 +2334,30 @@ The SDK also exports the following for advanced use cases. Most plugins don't ne
|
|
|
2254
2334
|
|
|
2255
2335
|
**Fields:**
|
|
2256
2336
|
|
|
2257
|
-
| Field | Req
|
|
2258
|
-
| --------------------- |
|
|
2259
|
-
| `name` | Yes
|
|
2260
|
-
| `version` | Yes
|
|
2261
|
-
| `description` | Yes
|
|
2262
|
-
| `expandedDescription` | No
|
|
2263
|
-
| `main` | Yes
|
|
2264
|
-
| `archeType` | Yes
|
|
2265
|
-
| `categorySlug` | No
|
|
2266
|
-
| `icon` | No
|
|
2267
|
-
| `tags` | No
|
|
2268
|
-
| `pricing` | Yes
|
|
2269
|
-
| `permissions` | Yes
|
|
2270
|
-
| `products` | No
|
|
2271
|
-
| `isPublic` | No
|
|
2272
|
-
| `isFullScreen` | No
|
|
2273
|
-
| `showSidebar` | No
|
|
2274
|
-
| `showExpandButton` | No
|
|
2275
|
-
| `isListed` | No
|
|
2276
|
-
| `
|
|
2277
|
-
| `
|
|
2278
|
-
| `
|
|
2337
|
+
| Field | Req | Description |
|
|
2338
|
+
| --------------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
2339
|
+
| `name` | Yes | Plugin identifier (lowercase, hyphens) |
|
|
2340
|
+
| `version` | Yes | Semver (e.g., `"1.0.0"`) |
|
|
2341
|
+
| `description` | Yes | Short marketplace description |
|
|
2342
|
+
| `expandedDescription` | No | Long-form marketplace copy |
|
|
2343
|
+
| `main` | Yes | Entry point source file |
|
|
2344
|
+
| `archeType` | Yes | `"tool"` or `"site"` |
|
|
2345
|
+
| `categorySlug` | No | Marketplace category for discovery |
|
|
2346
|
+
| `icon` | No | Path to an icon image **under `public/`** (e.g. `public/icon.png`) — only `src/` and `public/` are packaged. Applied on every publish (local, staging and prod) and rehosted as your marketplace/store icon. PNG, JPEG, WebP or SVG, ≤5 MB; raster images must also be ≤1024×1024 px (SVG has no pixel limit). An invalid icon rejects the submission: a first-publish listing fee is refunded, but the AI review that already ran is still billed. To change it, replace the file (or the path) and republish; removing the field keeps the current icon. |
|
|
2347
|
+
| `tags` | No | Discovery tags |
|
|
2348
|
+
| `pricing` | Yes | Marketplace **listing** pricing (see "Pricing detail" below). NOT for in-arche charges — use `products` |
|
|
2349
|
+
| `permissions` | Yes | Array of permission scopes (see below) |
|
|
2350
|
+
| `products` | No | IAP product catalog for `useFiasStore()` (see "Defining IAP products" below) |
|
|
2351
|
+
| `isPublic` | No | Default `false`. Anonymous visitors can view (see "Public arches" below) |
|
|
2352
|
+
| `isFullScreen` | No | Default `false`. Renders without platform chrome — implies `archeType: "site"` |
|
|
2353
|
+
| `showSidebar` | No | Default `true`. Set `false` to keep the platform top nav and drop the suggested-arches sidebar (see "Declining the sidebar" below) |
|
|
2354
|
+
| `showExpandButton` | No | Default `true`. Set `false` if you want neither the host's floating expand button nor your own (see "Controlling the host chrome" below) |
|
|
2355
|
+
| `isListed` | No | Default `true`. Visible in marketplace store |
|
|
2356
|
+
| `isGame` | No | Default `false`. `true` lists your arche on the Games page (`/a/games`) and badges it as a game in the Arche Store. Requires `game`. Omitting it on a republish takes the arche out of Games |
|
|
2357
|
+
| `game` | With `isGame` | `{ "genres": [...] }` — 1-4 (more only where a Fias admin allowed your arche) of `action`, `rpg`, `board`, `cards`, `casual`, `crafting`, `flying`, `match`, `platform`, `puzzle`, `strategy`, `word`, `racing`, `sports`, `simulation`, `trivia`, `music`, `educational`, `cozy`, `idle`, `tower-defense` (a kind of `strategy`: its games are listed there too). Refused without `isGame: true` |
|
|
2358
|
+
| `sdk` | Yes | SDK version range |
|
|
2359
|
+
| `dependencies` | No | npm packages with **exact** versions (max 20) |
|
|
2360
|
+
| `archeIds` | Auto | Per-environment IDs written by the CLI after first publish — don't hand-edit |
|
|
2279
2361
|
|
|
2280
2362
|
**Pricing detail:**
|
|
2281
2363
|
|
|
@@ -2290,7 +2372,7 @@ The SDK also exports the following for advanced use cases. Most plugins don't ne
|
|
|
2290
2372
|
|
|
2291
2373
|
Other optional sub-fields: `currency: "usd"` (only USD supported today), `platformFeePercent` (override the default platform cut — non-standard).
|
|
2292
2374
|
|
|
2293
|
-
**Permissions:** `theme:read`, `user:profile:read`, `storage:sandbox`, `storage:local`, `data:store`, `data:search`, `data:workspace`, `entities:invoke`, `entities:client_invoke`, `entities:image_generate`, `entities:image_edit`, `entities:audio_generate`, `entities:web_search`, `store:purchase`, `vault:documents:read`, `vault:documents:write`, `vault:user-documents:read`, `vault:user-documents:write`, `vault:user-documents:edit`, `vault:workspace-documents:read`, `vault:creative:read`, `vault:creative:write`, `assets:read`, `assets:community:read`, `assets:community:publish`, `litcraft:hub`, `navigation:open_arche`, `sandbox:vendored-libraries`, `ai:actions`
|
|
2375
|
+
**Permissions:** `theme:read`, `user:profile:read`, `storage:sandbox`, `storage:local`, `data:store`, `data:search`, `data:workspace`, `entities:invoke`, `entities:client_invoke`, `entities:image_generate`, `entities:image_edit`, `entities:audio_generate`, `entities:web_search`, `store:purchase`, `vault:documents:read`, `vault:documents:write`, `vault:user-documents:read`, `vault:user-documents:write`, `vault:user-documents:edit`, `vault:workspace-documents:read`, `vault:creative:read`, `vault:creative:write`, `opener:send`, `assets:read`, `assets:community:read`, `assets:community:publish`, `litcraft:hub`, `navigation:open_arche`, `sandbox:vendored-libraries`, `ai:actions`, `connections:use`, `schedule:invoke`, `web:sources:read`
|
|
2294
2376
|
|
|
2295
2377
|
**Using AI:** Add `"entities:invoke"` to permissions, then use `useEntityInvocation()` with a model entity ID (or a `{ capability }` selector, or an entry from `useTextModels()`) and your own `systemPrompt`. Browse available models with `npx fias-dev entities`.
|
|
2296
2378
|
|
|
@@ -2513,6 +2595,35 @@ npx fias-dev collaborators remove <identifier> # Revoke a collaborat
|
|
|
2513
2595
|
|
|
2514
2596
|
All commands accept `--env <prod|staging|local>` (default `prod`).
|
|
2515
2597
|
|
|
2598
|
+
### Extra Store listings (games inside your arche)
|
|
2599
|
+
|
|
2600
|
+
If your arche holds several games (or pages) that people should find on their own — Flappyland's mini-games, each at its own path — give each one an **extra Store listing**: its own title, description, square icon (also its iOS Home Screen icon) and genres, opening at its path. They show in Store search, on your Store page and on the Games page. Each listing uses a slot: **$50 once** (or a free listing an admin granted you); retiring one frees its slot for another, never refunds it. Your app must handle the path (`useFiasNavigation().currentPath`).
|
|
2601
|
+
|
|
2602
|
+
Manage them on your arche's settings page ("Store listings"), or from a `store-listings.json` file:
|
|
2603
|
+
|
|
2604
|
+
```json
|
|
2605
|
+
{
|
|
2606
|
+
"listings": [
|
|
2607
|
+
{
|
|
2608
|
+
"path": "/games/wordtrace",
|
|
2609
|
+
"title": "Wordtrace",
|
|
2610
|
+
"description": "Trace as many words as you can through a grid of letters.",
|
|
2611
|
+
"isGame": true,
|
|
2612
|
+
"genres": ["word"],
|
|
2613
|
+
"icon": "store-art/wordtrace.png"
|
|
2614
|
+
}
|
|
2615
|
+
]
|
|
2616
|
+
}
|
|
2617
|
+
```
|
|
2618
|
+
|
|
2619
|
+
```bash
|
|
2620
|
+
npx fias-dev listings list --env staging # Slots, live and retired listings
|
|
2621
|
+
npx fias-dev listings push --env staging # Show the plan and the charge, then ask
|
|
2622
|
+
npx fias-dev listings push --env staging --dry-run # Plan only
|
|
2623
|
+
```
|
|
2624
|
+
|
|
2625
|
+
Icons: square PNG, JPEG or WebP, 512×512 recommended (256–1024), up to 512 KB — keep them outside `public/` so they don't ship in your bundle. Every listing passes a content check (about 1 credit) before anything is charged; a refused one costs nothing else.
|
|
2626
|
+
|
|
2516
2627
|
### Other CLI diagnostics
|
|
2517
2628
|
|
|
2518
2629
|
```bash
|