@fias/create-fias-plugin 1.4.2 → 1.5.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 +188 -18
- package/templates/default/CLAUDE.md +188 -18
package/package.json
CHANGED
|
@@ -179,7 +179,9 @@ import { useFiasDataStore } from '@fias/arche-sdk';
|
|
|
179
179
|
function MyComponent() {
|
|
180
180
|
const dataStore = useFiasDataStore();
|
|
181
181
|
|
|
182
|
-
// Collection management
|
|
182
|
+
// Collection management. PREFER declaring collections in fias-plugin.json
|
|
183
|
+
// (see "Declaring your collections" below) — createCollection is capped at
|
|
184
|
+
// 10/minute, so scaffolding several at startup can silently lose the tail.
|
|
183
185
|
await dataStore.createCollection('scores', { userScope: 'user' }); // or 'shared'
|
|
184
186
|
const collections = await dataStore.listCollections();
|
|
185
187
|
await dataStore.deleteCollection('scores');
|
|
@@ -236,6 +238,24 @@ await dataStore.batch([
|
|
|
236
238
|
]);
|
|
237
239
|
```
|
|
238
240
|
|
|
241
|
+
### Declaring your collections (preferred)
|
|
242
|
+
|
|
243
|
+
List the collections your plugin needs in `fias-plugin.json` and the platform creates them once at publish, before your plugin ever runs:
|
|
244
|
+
|
|
245
|
+
```jsonc
|
|
246
|
+
"collections": [
|
|
247
|
+
{ "name": "scores", "scope": "user" },
|
|
248
|
+
{ "name": "ledger", "scope": "workspace", "readMinRole": "member" },
|
|
249
|
+
{ "name": "recipes", "scope": "shared", "writePolicy": "author", "searchable": { "field": "text" } }
|
|
250
|
+
]
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
`scope` is `user` | `shared` | `workspace`. `readMinRole` / `writeMinRole` are workspace-only; `writePolicy` is shared-only; `searchable` names the text field to embed. Same options as `createCollection`, checked by `fias-dev validate` before you publish.
|
|
254
|
+
|
|
255
|
+
Why this and not `createCollection` at startup: the runtime op is capped at **10 per minute** while an arche may hold **50** collections, so a plugin creating several on mount can exceed the cap and lose the rest **with no error** — surfacing later as `COLLECTION_NOT_FOUND` on an unrelated call. Declaring has no such window, and a mistake fails your publish instead of a user's session.
|
|
256
|
+
|
|
257
|
+
Re-publishing is safe: existing collections are left alone, a stricter role floor is applied, a looser one is refused, and a collection you stop declaring is kept (never deleted — it may hold user data). Keep using `createCollection` for collections whose names you only know at runtime.
|
|
258
|
+
|
|
239
259
|
**Rate limits (per minute):** `put` 120, `get` 300, `query` 60, `delete` 60, `batch` 30, `createCollection` 10
|
|
240
260
|
|
|
241
261
|
**Error handling:** Data store calls can reject on infrastructure errors or limit violations. Always use `try/catch`. Common error codes: `COLLECTION_NOT_FOUND`, `DOCUMENT_TOO_LARGE`, `STORAGE_LIMIT_REACHED`, `RATE_LIMIT`.
|
|
@@ -528,22 +548,6 @@ const { entities, isLoading } = useImageEntities({
|
|
|
528
548
|
|
|
529
549
|
Each entry is scrubbed — only fields a UI legitimately needs (no provider model strings, no endpoints, no execution config).
|
|
530
550
|
|
|
531
|
-
### `useBackgroundRemoval()` — Client-side background removal
|
|
532
|
-
|
|
533
|
-
**Permission:** `entities:image_remove_background`
|
|
534
|
-
**Returns:** `BackgroundRemovalApi`
|
|
535
|
-
|
|
536
|
-
Runs the imgly WASM model inside the host page — bytes never leave the user's device. Returns a transparent PNG.
|
|
537
|
-
|
|
538
|
-
```tsx
|
|
539
|
-
import { useBackgroundRemoval } from '@fias/arche-sdk';
|
|
540
|
-
|
|
541
|
-
const { removeBackground, isLoading } = useBackgroundRemoval();
|
|
542
|
-
const transparentPng = await removeBackground(jpegBlob);
|
|
543
|
-
```
|
|
544
|
-
|
|
545
|
-
**Limits:** input ≤ 25 MiB, PNG/JPEG only, rate-limited to 10/min per arche per user. Throws on size violation rather than returning a result.
|
|
546
|
-
|
|
547
551
|
### Image-editing capability hooks (auto-generated)
|
|
548
552
|
|
|
549
553
|
**Permission:** `entities:image_edit`
|
|
@@ -750,6 +754,52 @@ const { url } = await userDocs.getDownloadUrl(id); // binary docs (images, PDFs)
|
|
|
750
754
|
- Every read is audited by the platform; a new document _version_ is a new documentId, so a re-pick is needed after the user replaces a document.
|
|
751
755
|
- Not available in the builder preview or the dev harness — `pick()` resolves `{ canceled: true }` there. Test the flow in the published plugin with your own documents.
|
|
752
756
|
|
|
757
|
+
### `useFiasAIActions()` — Let the platform's Fias AI assistant operate your plugin
|
|
758
|
+
|
|
759
|
+
**Permission:** `ai:actions`
|
|
760
|
+
|
|
761
|
+
The platform assistant (the Fias AI button in the host header) can perform actions inside your plugin — but only ones you declare. Declared actions become the assistant's tools while your plugin is open; a user can say "rotate the selected pages" and the assistant calls your handler.
|
|
762
|
+
|
|
763
|
+
There are two declaration lanes, one shape:
|
|
764
|
+
|
|
765
|
+
- **Manifest (static, every plugin):** a `fiasAI: { description, actions: [...] }` block in `fias-plugin.json`. Fixed at publish and reviewed with your submission.
|
|
766
|
+
- **Runtime (this hook, trusted arches only):** declare/extend actions from code. The host silently ignores runtime declarations from non-trusted arches — manifest actions still work there, and this hook still handles their dispatches.
|
|
767
|
+
|
|
768
|
+
```tsx
|
|
769
|
+
import { useFiasAIActions } from '@fias/arche-sdk';
|
|
770
|
+
|
|
771
|
+
useFiasAIActions({
|
|
772
|
+
description: 'Once a document is open, the assistant can rotate its pages.',
|
|
773
|
+
actions: [
|
|
774
|
+
{
|
|
775
|
+
id: 'rotate_pages', // lowercase snake_case — becomes the tool name
|
|
776
|
+
description: 'Rotate the selected pages 90° clockwise.',
|
|
777
|
+
parameterSchema: { type: 'object', properties: {} },
|
|
778
|
+
availableWhen: { documentOpen: true }, // hidden until the state says so
|
|
779
|
+
},
|
|
780
|
+
],
|
|
781
|
+
state: { documentOpen: doc !== null }, // small, flag-shaped — NOT your data
|
|
782
|
+
onAction: async (actionId, params) => {
|
|
783
|
+
if (actionId === 'rotate_pages') {
|
|
784
|
+
if (!doc) throw new Error('No document is open.'); // ALWAYS re-check
|
|
785
|
+
await rotateSelected();
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
throw new Error(`Unknown action ${actionId}`);
|
|
789
|
+
},
|
|
790
|
+
});
|
|
791
|
+
```
|
|
792
|
+
|
|
793
|
+
Rules that matter:
|
|
794
|
+
|
|
795
|
+
- **Throw to report failure.** The assistant awaits `onAction` before telling the model the action applied — a batch ("do this to each file") stops on the failing step only if you throw.
|
|
796
|
+
- **Re-check preconditions in `onAction`.** `availableWhen` filtering is presentation; a stale turn can still dispatch a hidden verb.
|
|
797
|
+
- **Keep `state` tiny** (`{documentOpen: true}`, `hasPages: true`) — it is embedded in the assistant's prompt every turn and oversized snapshots are dropped.
|
|
798
|
+
- **UI state only.** Actions manipulate what the user could do in your interface; anything touching server state stays in your own code paths.
|
|
799
|
+
- `requiresConfirmation: true` makes the assistant ask the user before dispatching. It escalates only — nothing can skip a confirmation the platform requires.
|
|
800
|
+
- Manifest actions win id collisions with runtime ones; the merged list is capped at 50.
|
|
801
|
+
- The assistant is not present in the builder preview or the dev harness — declarations are accepted but nothing dispatches until the published plugin runs with the Fias AI window.
|
|
802
|
+
|
|
753
803
|
### `useArcheAssets()` — Contributor-published asset library
|
|
754
804
|
|
|
755
805
|
**Permission:** `assets:read`
|
|
@@ -772,6 +822,40 @@ const fresh = await getUrl(assetId);
|
|
|
772
822
|
|
|
773
823
|
Cache the `assetId`; refresh `signedUrl` via `getUrl()` rather than persisting URLs across sessions.
|
|
774
824
|
|
|
825
|
+
### `useCommunityAssets()` — User-published images, visible to this arche's users
|
|
826
|
+
|
|
827
|
+
**Permissions:** `assets:community:read` (browse/view) · `assets:community:publish` (publish/unpublish/listMine)
|
|
828
|
+
**Returns:** `CommunityAssetsApi`
|
|
829
|
+
|
|
830
|
+
The only way one user's content reaches another user. A user publishes an image **they created here**, and after moderation every authenticated user of **this arche** (and no other) can see it. Free to publish.
|
|
831
|
+
|
|
832
|
+
```tsx
|
|
833
|
+
import { useCommunityAssets, useImageGeneration } from '@fias/arche-sdk';
|
|
834
|
+
|
|
835
|
+
const { publish, publishMany, listCommunity, listMine, getUrls, unpublish } = useCommunityAssets();
|
|
836
|
+
const { generate } = useImageGeneration();
|
|
837
|
+
|
|
838
|
+
// 1. The user makes something, then shares it
|
|
839
|
+
const image = await generate({ prompt: 'a sleepy blue whale' });
|
|
840
|
+
const asset = await publish({ fileId: image.fileId, title: 'Sleepy whale', tags: ['page-art'] });
|
|
841
|
+
// asset.status === 'pending_moderation' ← NOT visible to others yet
|
|
842
|
+
|
|
843
|
+
// 2. Browse what the community published
|
|
844
|
+
const { assets, nextCursor } = await listCommunity({ limit: 30, tag: 'page-art' });
|
|
845
|
+
|
|
846
|
+
// 3. Sign a whole gallery in ONE call (URLs last ~5 min; the hook refreshes them)
|
|
847
|
+
const urls = await getUrls(assets.map((a) => a.assetId));
|
|
848
|
+
// urls[i]: { assetId, url, expiresAt } → drop straight into <img src={url} />
|
|
849
|
+
```
|
|
850
|
+
|
|
851
|
+
Three things to design around:
|
|
852
|
+
|
|
853
|
+
1. **Publishing is not instant.** `publish()` returns `status: 'pending_moderation'`. It shows up in `listCommunity()` only once approved — so show the author their own pending item via `listMine()` instead of optimistically adding it to the community feed.
|
|
854
|
+
2. **Batch your URLs.** Use `getUrls()` for a gallery or a multi-page book. Thirteen separate `getUrl()` calls against a ~5-minute TTL is the shape this exists to avoid. Ids that were taken down or aren't published yet are simply omitted — render around the gap.
|
|
855
|
+
3. **Structure goes in the Data Store.** Which asset belongs on which page/level lives in a `shared`-scope Data Store collection holding `assetId`s. Community Assets stores the bytes and nothing queryable — `tags` plus cursor paging is the whole query surface, deliberately.
|
|
856
|
+
|
|
857
|
+
Only images the user made or uploaded in **their own** Fias files can be published (pass the `fileId` from a `useImageGeneration()` result). PNG/JPEG/WebP only; every image is re-encoded to a canonical PNG with metadata stripped. Publishers can `unpublish()` their own assets, and any user can `report()` one for review.
|
|
858
|
+
|
|
775
859
|
### `useFiasStore()` — In-app purchases (IAP)
|
|
776
860
|
|
|
777
861
|
**Permission:** `store:purchase`
|
|
@@ -941,6 +1025,91 @@ await fias.dataStore.listCollections();
|
|
|
941
1025
|
await fias.dataStore.deleteCollection('scores');
|
|
942
1026
|
```
|
|
943
1027
|
|
|
1028
|
+
### Panels (dialogs, reward popups, empty states)
|
|
1029
|
+
|
|
1030
|
+
A **panel** is a bounded block of declarative content — a title, an ordered list of leaf
|
|
1031
|
+
elements, some buttons. The platform ships a shared renderer, `@fias/panel-kit`, from its own
|
|
1032
|
+
CDN, so you get the same dialog/reward-popup look as first-party arches without writing modal,
|
|
1033
|
+
scrim or focus-trap code.
|
|
1034
|
+
|
|
1035
|
+
**Opt in** with a permission and a dependency (the platform serves the module; do NOT install it
|
|
1036
|
+
from npm — it isn't there):
|
|
1037
|
+
|
|
1038
|
+
```json
|
|
1039
|
+
// fias-plugin.json
|
|
1040
|
+
{
|
|
1041
|
+
"permissions": ["sandbox:vendored-libraries"],
|
|
1042
|
+
"dependencies": { "@fias/panel-kit": "0.3.0" }
|
|
1043
|
+
}
|
|
1044
|
+
```
|
|
1045
|
+
|
|
1046
|
+
The build **pins the version from the platform registry and rejects a manifest that declares a
|
|
1047
|
+
different one**, with the exact string to write. When a new panel-kit ships, bump this on your
|
|
1048
|
+
next submission — already-published builds keep resolving the version they were built against.
|
|
1049
|
+
|
|
1050
|
+
**Render one.** Inline is just a component; modal goes through the host:
|
|
1051
|
+
|
|
1052
|
+
```tsx
|
|
1053
|
+
import { Panel, PanelHost, usePanel } from '@fias/panel-kit';
|
|
1054
|
+
|
|
1055
|
+
// Inline — in the page's normal flow.
|
|
1056
|
+
<Panel definition={definition} context={{ values: { score }, items }} onAction={handleAction} />;
|
|
1057
|
+
|
|
1058
|
+
// Modal — mount <PanelHost> once near your app root, then:
|
|
1059
|
+
const { showPanel } = usePanel();
|
|
1060
|
+
const outcome = await showPanel(definition, { context: { values, items } });
|
|
1061
|
+
if (outcome.type === 'emit' && outcome.token === 'restart') restart();
|
|
1062
|
+
```
|
|
1063
|
+
|
|
1064
|
+
`onAction` / the resolved outcome hands you the parsed intent — the renderer never navigates. A
|
|
1065
|
+
button's `action` is a closed namespace: `close`, `emit:<token>` (your own verb, handed back to
|
|
1066
|
+
you), `open_arche:arc_<32 hex>`, `open_url:https://…`.
|
|
1067
|
+
|
|
1068
|
+
**Elements** are leaf kinds only — no nesting, no layout algebra, no conditionals beyond one
|
|
1069
|
+
truthiness check:
|
|
1070
|
+
|
|
1071
|
+
| Kind | What it draws |
|
|
1072
|
+
| ----------------- | --------------------------------------------------------------------------------- |
|
|
1073
|
+
| `text` | Plain text; `{placeholder}` tokens fill from `context.values`. Never HTML. |
|
|
1074
|
+
| `conditionalText` | The same, only when `context.values[showIf]` is truthy. `showIf` is a value NAME. |
|
|
1075
|
+
| `image` | A platform storage key the host resolves. |
|
|
1076
|
+
| `assetImage` | An arche asset-library id (`as_…`). |
|
|
1077
|
+
| `items` | `context.items` — the list you pass at render time. |
|
|
1078
|
+
| `slottedItems` | `context.itemsBySlot[slot]` — several independent lists in one panel. |
|
|
1079
|
+
| `button` | Label + action. |
|
|
1080
|
+
| `spacer` | Vertical whitespace. |
|
|
1081
|
+
|
|
1082
|
+
Both text kinds take `joinNext: true` to share a line with the element after them
|
|
1083
|
+
(`Base Reward: [icon] Fabricanse ×1`).
|
|
1084
|
+
|
|
1085
|
+
**Theming.** A `theme` sets colours, font, radii, spacing and the quantity format. Pass it to the
|
|
1086
|
+
presenter — not to a wrapping element — because the modal portals out of your subtree:
|
|
1087
|
+
|
|
1088
|
+
```tsx
|
|
1089
|
+
<PanelHost theme={{ bg: '#F6EAD2', accent: '#D8B24A', amountFormat: '×{n}' }} />
|
|
1090
|
+
```
|
|
1091
|
+
|
|
1092
|
+
Values are grammars, not free CSS: hex or `rgb()/rgba()` colours, `px`/`rem`/`em` lengths, a font
|
|
1093
|
+
stack with no parentheses. Anything else is dropped (a `url()` in a custom property would make
|
|
1094
|
+
every panel you draw fetch a third-party asset).
|
|
1095
|
+
|
|
1096
|
+
**Panels as DATA.** If your panels live in storage rather than in code — a `dataStore` document,
|
|
1097
|
+
an admin-edited blob — validate on the WRITE, using the same rules the platform enforces:
|
|
1098
|
+
|
|
1099
|
+
```ts
|
|
1100
|
+
import { validatePanelCatalog } from '@fias/arche-sdk';
|
|
1101
|
+
|
|
1102
|
+
const result = validatePanelCatalog(draft); // { panels: [...], theme?: {...} }
|
|
1103
|
+
if (!result.ok) return showErrors(result.issues); // one message per problem, path-prefixed
|
|
1104
|
+
await fias.dataStore.put('panels', 'catalog', draft);
|
|
1105
|
+
```
|
|
1106
|
+
|
|
1107
|
+
That is the whole pattern for server-driven panel content: **your existing storage plus a shared
|
|
1108
|
+
validator** — no new permission, no platform call. Changing a panel's wording then means editing
|
|
1109
|
+
a document, not shipping a release. `validatePanelDefinition` and `validatePanelTheme` validate
|
|
1110
|
+
the parts individually; the types (`PanelDefinition`, `PanelElement`, `PanelTheme`, `PanelCatalog`)
|
|
1111
|
+
come from `@fias/arche-sdk` too, so the shape you store is the shape the renderer takes.
|
|
1112
|
+
|
|
944
1113
|
### Advanced exports (rarely needed)
|
|
945
1114
|
|
|
946
1115
|
The SDK also exports the following for advanced use cases. Most plugins don't need them.
|
|
@@ -1002,7 +1171,7 @@ The SDK also exports the following for advanced use cases. Most plugins don't ne
|
|
|
1002
1171
|
|
|
1003
1172
|
Other optional sub-fields: `currency: "usd"` (only USD supported today), `platformFeePercent` (override the default platform cut — non-standard).
|
|
1004
1173
|
|
|
1005
|
-
**Permissions:** `theme:read`, `user:profile:read`, `storage:sandbox`, `data:store`, `data:search`, `entities:invoke`, `entities:client_invoke`, `entities:image_generate`, `entities:image_edit`, `entities:
|
|
1174
|
+
**Permissions:** `theme:read`, `user:profile:read`, `storage:sandbox`, `data:store`, `data:search`, `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`, `assets:read`, `navigation:open_arche`, `sandbox:vendored-libraries`
|
|
1006
1175
|
|
|
1007
1176
|
**Using AI:** Add `"entities:invoke"` to permissions, then use `useEntityInvocation()` with a model entity ID and your own `systemPrompt`. Browse available models with `npx fias-dev entities`.
|
|
1008
1177
|
|
|
@@ -1053,6 +1222,7 @@ When you hit a constraint, say so AND tell the user: file an Entity request on t
|
|
|
1053
1222
|
### Sandboxing
|
|
1054
1223
|
|
|
1055
1224
|
- Plugins run in an iframe with `sandbox="allow-scripts allow-forms allow-downloads allow-popups allow-popups-to-escape-sandbox"` (note: **no** `allow-same-origin` — the plugin has an opaque origin)
|
|
1225
|
+
- **`window.alert()` / `confirm()` / `prompt()` do nothing** (no `allow-modals`): they don't throw — `confirm()` silently returns `false`, so gated actions never run. Build confirmation into your UI (a two-step button or an inline dialog) instead
|
|
1056
1226
|
- **No `fetch()` or `XMLHttpRequest`** — all network access is blocked
|
|
1057
1227
|
- **You _can_ open external links in a new tab** (e.g. `window.open('https://…', '_blank')`) — see "Opening external links" above
|
|
1058
1228
|
- **No access** to parent DOM, cookies, or localStorage
|
|
@@ -179,7 +179,9 @@ import { useFiasDataStore } from '@fias/arche-sdk';
|
|
|
179
179
|
function MyComponent() {
|
|
180
180
|
const dataStore = useFiasDataStore();
|
|
181
181
|
|
|
182
|
-
// Collection management
|
|
182
|
+
// Collection management. PREFER declaring collections in fias-plugin.json
|
|
183
|
+
// (see "Declaring your collections" below) — createCollection is capped at
|
|
184
|
+
// 10/minute, so scaffolding several at startup can silently lose the tail.
|
|
183
185
|
await dataStore.createCollection('scores', { userScope: 'user' }); // or 'shared'
|
|
184
186
|
const collections = await dataStore.listCollections();
|
|
185
187
|
await dataStore.deleteCollection('scores');
|
|
@@ -236,6 +238,24 @@ await dataStore.batch([
|
|
|
236
238
|
]);
|
|
237
239
|
```
|
|
238
240
|
|
|
241
|
+
### Declaring your collections (preferred)
|
|
242
|
+
|
|
243
|
+
List the collections your plugin needs in `fias-plugin.json` and the platform creates them once at publish, before your plugin ever runs:
|
|
244
|
+
|
|
245
|
+
```jsonc
|
|
246
|
+
"collections": [
|
|
247
|
+
{ "name": "scores", "scope": "user" },
|
|
248
|
+
{ "name": "ledger", "scope": "workspace", "readMinRole": "member" },
|
|
249
|
+
{ "name": "recipes", "scope": "shared", "writePolicy": "author", "searchable": { "field": "text" } }
|
|
250
|
+
]
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
`scope` is `user` | `shared` | `workspace`. `readMinRole` / `writeMinRole` are workspace-only; `writePolicy` is shared-only; `searchable` names the text field to embed. Same options as `createCollection`, checked by `fias-dev validate` before you publish.
|
|
254
|
+
|
|
255
|
+
Why this and not `createCollection` at startup: the runtime op is capped at **10 per minute** while an arche may hold **50** collections, so a plugin creating several on mount can exceed the cap and lose the rest **with no error** — surfacing later as `COLLECTION_NOT_FOUND` on an unrelated call. Declaring has no such window, and a mistake fails your publish instead of a user's session.
|
|
256
|
+
|
|
257
|
+
Re-publishing is safe: existing collections are left alone, a stricter role floor is applied, a looser one is refused, and a collection you stop declaring is kept (never deleted — it may hold user data). Keep using `createCollection` for collections whose names you only know at runtime.
|
|
258
|
+
|
|
239
259
|
**Rate limits (per minute):** `put` 120, `get` 300, `query` 60, `delete` 60, `batch` 30, `createCollection` 10
|
|
240
260
|
|
|
241
261
|
**Error handling:** Data store calls can reject on infrastructure errors or limit violations. Always use `try/catch`. Common error codes: `COLLECTION_NOT_FOUND`, `DOCUMENT_TOO_LARGE`, `STORAGE_LIMIT_REACHED`, `RATE_LIMIT`.
|
|
@@ -528,22 +548,6 @@ const { entities, isLoading } = useImageEntities({
|
|
|
528
548
|
|
|
529
549
|
Each entry is scrubbed — only fields a UI legitimately needs (no provider model strings, no endpoints, no execution config).
|
|
530
550
|
|
|
531
|
-
### `useBackgroundRemoval()` — Client-side background removal
|
|
532
|
-
|
|
533
|
-
**Permission:** `entities:image_remove_background`
|
|
534
|
-
**Returns:** `BackgroundRemovalApi`
|
|
535
|
-
|
|
536
|
-
Runs the imgly WASM model inside the host page — bytes never leave the user's device. Returns a transparent PNG.
|
|
537
|
-
|
|
538
|
-
```tsx
|
|
539
|
-
import { useBackgroundRemoval } from '@fias/arche-sdk';
|
|
540
|
-
|
|
541
|
-
const { removeBackground, isLoading } = useBackgroundRemoval();
|
|
542
|
-
const transparentPng = await removeBackground(jpegBlob);
|
|
543
|
-
```
|
|
544
|
-
|
|
545
|
-
**Limits:** input ≤ 25 MiB, PNG/JPEG only, rate-limited to 10/min per arche per user. Throws on size violation rather than returning a result.
|
|
546
|
-
|
|
547
551
|
### Image-editing capability hooks (auto-generated)
|
|
548
552
|
|
|
549
553
|
**Permission:** `entities:image_edit`
|
|
@@ -750,6 +754,52 @@ const { url } = await userDocs.getDownloadUrl(id); // binary docs (images, PDFs)
|
|
|
750
754
|
- Every read is audited by the platform; a new document _version_ is a new documentId, so a re-pick is needed after the user replaces a document.
|
|
751
755
|
- Not available in the builder preview or the dev harness — `pick()` resolves `{ canceled: true }` there. Test the flow in the published plugin with your own documents.
|
|
752
756
|
|
|
757
|
+
### `useFiasAIActions()` — Let the platform's Fias AI assistant operate your plugin
|
|
758
|
+
|
|
759
|
+
**Permission:** `ai:actions`
|
|
760
|
+
|
|
761
|
+
The platform assistant (the Fias AI button in the host header) can perform actions inside your plugin — but only ones you declare. Declared actions become the assistant's tools while your plugin is open; a user can say "rotate the selected pages" and the assistant calls your handler.
|
|
762
|
+
|
|
763
|
+
There are two declaration lanes, one shape:
|
|
764
|
+
|
|
765
|
+
- **Manifest (static, every plugin):** a `fiasAI: { description, actions: [...] }` block in `fias-plugin.json`. Fixed at publish and reviewed with your submission.
|
|
766
|
+
- **Runtime (this hook, trusted arches only):** declare/extend actions from code. The host silently ignores runtime declarations from non-trusted arches — manifest actions still work there, and this hook still handles their dispatches.
|
|
767
|
+
|
|
768
|
+
```tsx
|
|
769
|
+
import { useFiasAIActions } from '@fias/arche-sdk';
|
|
770
|
+
|
|
771
|
+
useFiasAIActions({
|
|
772
|
+
description: 'Once a document is open, the assistant can rotate its pages.',
|
|
773
|
+
actions: [
|
|
774
|
+
{
|
|
775
|
+
id: 'rotate_pages', // lowercase snake_case — becomes the tool name
|
|
776
|
+
description: 'Rotate the selected pages 90° clockwise.',
|
|
777
|
+
parameterSchema: { type: 'object', properties: {} },
|
|
778
|
+
availableWhen: { documentOpen: true }, // hidden until the state says so
|
|
779
|
+
},
|
|
780
|
+
],
|
|
781
|
+
state: { documentOpen: doc !== null }, // small, flag-shaped — NOT your data
|
|
782
|
+
onAction: async (actionId, params) => {
|
|
783
|
+
if (actionId === 'rotate_pages') {
|
|
784
|
+
if (!doc) throw new Error('No document is open.'); // ALWAYS re-check
|
|
785
|
+
await rotateSelected();
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
throw new Error(`Unknown action ${actionId}`);
|
|
789
|
+
},
|
|
790
|
+
});
|
|
791
|
+
```
|
|
792
|
+
|
|
793
|
+
Rules that matter:
|
|
794
|
+
|
|
795
|
+
- **Throw to report failure.** The assistant awaits `onAction` before telling the model the action applied — a batch ("do this to each file") stops on the failing step only if you throw.
|
|
796
|
+
- **Re-check preconditions in `onAction`.** `availableWhen` filtering is presentation; a stale turn can still dispatch a hidden verb.
|
|
797
|
+
- **Keep `state` tiny** (`{documentOpen: true}`, `hasPages: true`) — it is embedded in the assistant's prompt every turn and oversized snapshots are dropped.
|
|
798
|
+
- **UI state only.** Actions manipulate what the user could do in your interface; anything touching server state stays in your own code paths.
|
|
799
|
+
- `requiresConfirmation: true` makes the assistant ask the user before dispatching. It escalates only — nothing can skip a confirmation the platform requires.
|
|
800
|
+
- Manifest actions win id collisions with runtime ones; the merged list is capped at 50.
|
|
801
|
+
- The assistant is not present in the builder preview or the dev harness — declarations are accepted but nothing dispatches until the published plugin runs with the Fias AI window.
|
|
802
|
+
|
|
753
803
|
### `useArcheAssets()` — Contributor-published asset library
|
|
754
804
|
|
|
755
805
|
**Permission:** `assets:read`
|
|
@@ -772,6 +822,40 @@ const fresh = await getUrl(assetId);
|
|
|
772
822
|
|
|
773
823
|
Cache the `assetId`; refresh `signedUrl` via `getUrl()` rather than persisting URLs across sessions.
|
|
774
824
|
|
|
825
|
+
### `useCommunityAssets()` — User-published images, visible to this arche's users
|
|
826
|
+
|
|
827
|
+
**Permissions:** `assets:community:read` (browse/view) · `assets:community:publish` (publish/unpublish/listMine)
|
|
828
|
+
**Returns:** `CommunityAssetsApi`
|
|
829
|
+
|
|
830
|
+
The only way one user's content reaches another user. A user publishes an image **they created here**, and after moderation every authenticated user of **this arche** (and no other) can see it. Free to publish.
|
|
831
|
+
|
|
832
|
+
```tsx
|
|
833
|
+
import { useCommunityAssets, useImageGeneration } from '@fias/arche-sdk';
|
|
834
|
+
|
|
835
|
+
const { publish, publishMany, listCommunity, listMine, getUrls, unpublish } = useCommunityAssets();
|
|
836
|
+
const { generate } = useImageGeneration();
|
|
837
|
+
|
|
838
|
+
// 1. The user makes something, then shares it
|
|
839
|
+
const image = await generate({ prompt: 'a sleepy blue whale' });
|
|
840
|
+
const asset = await publish({ fileId: image.fileId, title: 'Sleepy whale', tags: ['page-art'] });
|
|
841
|
+
// asset.status === 'pending_moderation' ← NOT visible to others yet
|
|
842
|
+
|
|
843
|
+
// 2. Browse what the community published
|
|
844
|
+
const { assets, nextCursor } = await listCommunity({ limit: 30, tag: 'page-art' });
|
|
845
|
+
|
|
846
|
+
// 3. Sign a whole gallery in ONE call (URLs last ~5 min; the hook refreshes them)
|
|
847
|
+
const urls = await getUrls(assets.map((a) => a.assetId));
|
|
848
|
+
// urls[i]: { assetId, url, expiresAt } → drop straight into <img src={url} />
|
|
849
|
+
```
|
|
850
|
+
|
|
851
|
+
Three things to design around:
|
|
852
|
+
|
|
853
|
+
1. **Publishing is not instant.** `publish()` returns `status: 'pending_moderation'`. It shows up in `listCommunity()` only once approved — so show the author their own pending item via `listMine()` instead of optimistically adding it to the community feed.
|
|
854
|
+
2. **Batch your URLs.** Use `getUrls()` for a gallery or a multi-page book. Thirteen separate `getUrl()` calls against a ~5-minute TTL is the shape this exists to avoid. Ids that were taken down or aren't published yet are simply omitted — render around the gap.
|
|
855
|
+
3. **Structure goes in the Data Store.** Which asset belongs on which page/level lives in a `shared`-scope Data Store collection holding `assetId`s. Community Assets stores the bytes and nothing queryable — `tags` plus cursor paging is the whole query surface, deliberately.
|
|
856
|
+
|
|
857
|
+
Only images the user made or uploaded in **their own** Fias files can be published (pass the `fileId` from a `useImageGeneration()` result). PNG/JPEG/WebP only; every image is re-encoded to a canonical PNG with metadata stripped. Publishers can `unpublish()` their own assets, and any user can `report()` one for review.
|
|
858
|
+
|
|
775
859
|
### `useFiasStore()` — In-app purchases (IAP)
|
|
776
860
|
|
|
777
861
|
**Permission:** `store:purchase`
|
|
@@ -941,6 +1025,91 @@ await fias.dataStore.listCollections();
|
|
|
941
1025
|
await fias.dataStore.deleteCollection('scores');
|
|
942
1026
|
```
|
|
943
1027
|
|
|
1028
|
+
### Panels (dialogs, reward popups, empty states)
|
|
1029
|
+
|
|
1030
|
+
A **panel** is a bounded block of declarative content — a title, an ordered list of leaf
|
|
1031
|
+
elements, some buttons. The platform ships a shared renderer, `@fias/panel-kit`, from its own
|
|
1032
|
+
CDN, so you get the same dialog/reward-popup look as first-party arches without writing modal,
|
|
1033
|
+
scrim or focus-trap code.
|
|
1034
|
+
|
|
1035
|
+
**Opt in** with a permission and a dependency (the platform serves the module; do NOT install it
|
|
1036
|
+
from npm — it isn't there):
|
|
1037
|
+
|
|
1038
|
+
```json
|
|
1039
|
+
// fias-plugin.json
|
|
1040
|
+
{
|
|
1041
|
+
"permissions": ["sandbox:vendored-libraries"],
|
|
1042
|
+
"dependencies": { "@fias/panel-kit": "0.3.0" }
|
|
1043
|
+
}
|
|
1044
|
+
```
|
|
1045
|
+
|
|
1046
|
+
The build **pins the version from the platform registry and rejects a manifest that declares a
|
|
1047
|
+
different one**, with the exact string to write. When a new panel-kit ships, bump this on your
|
|
1048
|
+
next submission — already-published builds keep resolving the version they were built against.
|
|
1049
|
+
|
|
1050
|
+
**Render one.** Inline is just a component; modal goes through the host:
|
|
1051
|
+
|
|
1052
|
+
```tsx
|
|
1053
|
+
import { Panel, PanelHost, usePanel } from '@fias/panel-kit';
|
|
1054
|
+
|
|
1055
|
+
// Inline — in the page's normal flow.
|
|
1056
|
+
<Panel definition={definition} context={{ values: { score }, items }} onAction={handleAction} />;
|
|
1057
|
+
|
|
1058
|
+
// Modal — mount <PanelHost> once near your app root, then:
|
|
1059
|
+
const { showPanel } = usePanel();
|
|
1060
|
+
const outcome = await showPanel(definition, { context: { values, items } });
|
|
1061
|
+
if (outcome.type === 'emit' && outcome.token === 'restart') restart();
|
|
1062
|
+
```
|
|
1063
|
+
|
|
1064
|
+
`onAction` / the resolved outcome hands you the parsed intent — the renderer never navigates. A
|
|
1065
|
+
button's `action` is a closed namespace: `close`, `emit:<token>` (your own verb, handed back to
|
|
1066
|
+
you), `open_arche:arc_<32 hex>`, `open_url:https://…`.
|
|
1067
|
+
|
|
1068
|
+
**Elements** are leaf kinds only — no nesting, no layout algebra, no conditionals beyond one
|
|
1069
|
+
truthiness check:
|
|
1070
|
+
|
|
1071
|
+
| Kind | What it draws |
|
|
1072
|
+
| ----------------- | --------------------------------------------------------------------------------- |
|
|
1073
|
+
| `text` | Plain text; `{placeholder}` tokens fill from `context.values`. Never HTML. |
|
|
1074
|
+
| `conditionalText` | The same, only when `context.values[showIf]` is truthy. `showIf` is a value NAME. |
|
|
1075
|
+
| `image` | A platform storage key the host resolves. |
|
|
1076
|
+
| `assetImage` | An arche asset-library id (`as_…`). |
|
|
1077
|
+
| `items` | `context.items` — the list you pass at render time. |
|
|
1078
|
+
| `slottedItems` | `context.itemsBySlot[slot]` — several independent lists in one panel. |
|
|
1079
|
+
| `button` | Label + action. |
|
|
1080
|
+
| `spacer` | Vertical whitespace. |
|
|
1081
|
+
|
|
1082
|
+
Both text kinds take `joinNext: true` to share a line with the element after them
|
|
1083
|
+
(`Base Reward: [icon] Fabricanse ×1`).
|
|
1084
|
+
|
|
1085
|
+
**Theming.** A `theme` sets colours, font, radii, spacing and the quantity format. Pass it to the
|
|
1086
|
+
presenter — not to a wrapping element — because the modal portals out of your subtree:
|
|
1087
|
+
|
|
1088
|
+
```tsx
|
|
1089
|
+
<PanelHost theme={{ bg: '#F6EAD2', accent: '#D8B24A', amountFormat: '×{n}' }} />
|
|
1090
|
+
```
|
|
1091
|
+
|
|
1092
|
+
Values are grammars, not free CSS: hex or `rgb()/rgba()` colours, `px`/`rem`/`em` lengths, a font
|
|
1093
|
+
stack with no parentheses. Anything else is dropped (a `url()` in a custom property would make
|
|
1094
|
+
every panel you draw fetch a third-party asset).
|
|
1095
|
+
|
|
1096
|
+
**Panels as DATA.** If your panels live in storage rather than in code — a `dataStore` document,
|
|
1097
|
+
an admin-edited blob — validate on the WRITE, using the same rules the platform enforces:
|
|
1098
|
+
|
|
1099
|
+
```ts
|
|
1100
|
+
import { validatePanelCatalog } from '@fias/arche-sdk';
|
|
1101
|
+
|
|
1102
|
+
const result = validatePanelCatalog(draft); // { panels: [...], theme?: {...} }
|
|
1103
|
+
if (!result.ok) return showErrors(result.issues); // one message per problem, path-prefixed
|
|
1104
|
+
await fias.dataStore.put('panels', 'catalog', draft);
|
|
1105
|
+
```
|
|
1106
|
+
|
|
1107
|
+
That is the whole pattern for server-driven panel content: **your existing storage plus a shared
|
|
1108
|
+
validator** — no new permission, no platform call. Changing a panel's wording then means editing
|
|
1109
|
+
a document, not shipping a release. `validatePanelDefinition` and `validatePanelTheme` validate
|
|
1110
|
+
the parts individually; the types (`PanelDefinition`, `PanelElement`, `PanelTheme`, `PanelCatalog`)
|
|
1111
|
+
come from `@fias/arche-sdk` too, so the shape you store is the shape the renderer takes.
|
|
1112
|
+
|
|
944
1113
|
### Advanced exports (rarely needed)
|
|
945
1114
|
|
|
946
1115
|
The SDK also exports the following for advanced use cases. Most plugins don't need them.
|
|
@@ -1002,7 +1171,7 @@ The SDK also exports the following for advanced use cases. Most plugins don't ne
|
|
|
1002
1171
|
|
|
1003
1172
|
Other optional sub-fields: `currency: "usd"` (only USD supported today), `platformFeePercent` (override the default platform cut — non-standard).
|
|
1004
1173
|
|
|
1005
|
-
**Permissions:** `theme:read`, `user:profile:read`, `storage:sandbox`, `data:store`, `data:search`, `entities:invoke`, `entities:client_invoke`, `entities:image_generate`, `entities:image_edit`, `entities:
|
|
1174
|
+
**Permissions:** `theme:read`, `user:profile:read`, `storage:sandbox`, `data:store`, `data:search`, `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`, `assets:read`, `navigation:open_arche`, `sandbox:vendored-libraries`
|
|
1006
1175
|
|
|
1007
1176
|
**Using AI:** Add `"entities:invoke"` to permissions, then use `useEntityInvocation()` with a model entity ID and your own `systemPrompt`. Browse available models with `npx fias-dev entities`.
|
|
1008
1177
|
|
|
@@ -1053,6 +1222,7 @@ When you hit a constraint, say so AND tell the user: file an Entity request on t
|
|
|
1053
1222
|
### Sandboxing
|
|
1054
1223
|
|
|
1055
1224
|
- Plugins run in an iframe with `sandbox="allow-scripts allow-forms allow-downloads allow-popups allow-popups-to-escape-sandbox"` (note: **no** `allow-same-origin` — the plugin has an opaque origin)
|
|
1225
|
+
- **`window.alert()` / `confirm()` / `prompt()` do nothing** (no `allow-modals`): they don't throw — `confirm()` silently returns `false`, so gated actions never run. Build confirmation into your UI (a two-step button or an inline dialog) instead
|
|
1056
1226
|
- **No `fetch()` or `XMLHttpRequest`** — all network access is blocked
|
|
1057
1227
|
- **You _can_ open external links in a new tab** (e.g. `window.open('https://…', '_blank')`) — see "Opening external links" above
|
|
1058
1228
|
- **No access** to parent DOM, cookies, or localStorage
|