flare-plugin-sdk 0.0.0-stage → 0.1.0-beta.2

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 (27) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +131 -2
  3. package/package.json +33 -4
  4. package/skill/flare-plugin/ABI.md +334 -0
  5. package/skill/flare-plugin/README.md +204 -0
  6. package/skill/flare-plugin/SKILL.md +80 -0
  7. package/skill/flare-plugin/assets/github-template/README.md +71 -0
  8. package/skill/flare-plugin/assets/github-template/assets/icon.png +0 -0
  9. package/skill/flare-plugin/assets/github-template/manifest.json +29 -0
  10. package/skill/flare-plugin/assets/github-template/plugin.js +108 -0
  11. package/skill/flare-plugin/examples/pixelfed/README.md +78 -0
  12. package/skill/flare-plugin/examples/pixelfed/assets/icon.png +0 -0
  13. package/skill/flare-plugin/examples/pixelfed/locales/en.json +24 -0
  14. package/skill/flare-plugin/examples/pixelfed/locales/zh-Hans.json +24 -0
  15. package/skill/flare-plugin/examples/pixelfed/manifest.json +211 -0
  16. package/skill/flare-plugin/examples/pixelfed/plugin.js +793 -0
  17. package/skill/flare-plugin/flare-plugin.d.ts +501 -0
  18. package/skill/flare-plugin/pack.mjs +155 -0
  19. package/skill/flare-plugin/references/authentication.md +239 -0
  20. package/skill/flare-plugin/references/capabilities.md +88 -0
  21. package/skill/flare-plugin/references/manifest.md +192 -0
  22. package/skill/flare-plugin/references/publishing.md +111 -0
  23. package/skill/flare-plugin/references/recipes.md +328 -0
  24. package/skill/flare-plugin/references/runtime-http.md +207 -0
  25. package/skill/flare-plugin/references/testing.md +176 -0
  26. package/skill/flare-plugin/references/troubleshooting.md +116 -0
  27. package/skill/flare-plugin/references/wire-data.md +163 -0
@@ -0,0 +1,239 @@
1
+ # Detection and authentication recipes
2
+
3
+ These recipes describe the five supported interaction kinds. Website paths and
4
+ helpers in sketches are adaptation points, not universal endpoints. Use the
5
+ [GitHub template](../assets/github-template/plugin.js) for a complete token-login
6
+ implementation and [Pixelfed](../examples/pixelfed/plugin.js) for complete OAuth.
7
+ Never accept a submitted token/password as proof of identity without verifying
8
+ it against the website.
9
+
10
+ ## Detection
11
+
12
+ Declare `platform.detector: { "priority": 0 }` only when implementing
13
+ `detector.detect(request, context)`. Inspect a documented endpoint on the
14
+ candidate origin. Return `match: "none"` when evidence does not match;
15
+ `"exact"` for the intended software and `"compatible"` only for known compatible
16
+ behavior. Return the canonical HTTPS origin and optional software/instance data:
17
+
18
+ ```js
19
+ // Result after verifying the website's software identity.
20
+ const detected = {
21
+ match: "exact",
22
+ canonicalOrigin: context.origin,
23
+ software: "ExampleSoftware",
24
+ instance: { domain: context.origin.slice("https://".length), title: "Example" },
25
+ };
26
+ ```
27
+
28
+ Do not claim every reachable host. Detection has no credential access and may
29
+ request only the candidate origin. Omit the declaration and method if explicit
30
+ platform selection is sufficient, as in the template.
31
+
32
+ ## State transitions
33
+
34
+ The method key is the manifest method **ID**, not necessarily its interaction
35
+ name. `id: "token"` registers `login.token.begin`, for example.
36
+
37
+ | Interaction | Calls | Allowed outcome |
38
+ | --- | --- | --- |
39
+ | OAuth | begin → browser callback → resume | begin: externalBrowser or success; resume: success |
40
+ | Password | begin(values) | success |
41
+ | CredentialImport | begin(values) | success |
42
+ | Form | begin(values) | success |
43
+ | WebCookie | begin → repeated check(snapshot) | begin: webCookie; check: pending or success |
44
+
45
+ `pending` is not a general multi-step form mechanism. A form is submitted once;
46
+ include all fields required to complete that submission. Expected login errors
47
+ use `flare.error`; do not return an invented `error` transition.
48
+
49
+ All successful methods return:
50
+
51
+ ```js
52
+ // profile and credential come from a verified remote login.
53
+ const success = {
54
+ type: "success",
55
+ value: {
56
+ accountId: profile.key.id,
57
+ origin: context.origin,
58
+ credential,
59
+ profile,
60
+ capabilities: { "flare.datasource.timeline/v1": ["page"] },
61
+ },
62
+ };
63
+ ```
64
+
65
+ `accountId` equals `profile.key.id`; the profile host equals the selected origin's
66
+ host, including a non-default port. The Host checks origin, account ownership,
67
+ and relogin identity. At least one displayable, declared capability must be
68
+ negotiated with its required operation set. Login can return `composeConfig`
69
+ using the [documented merge rules](manifest.md#compose-defaults). Preserve only credentials needed for later requests;
70
+ avoid retaining a password if the server issues a reusable token instead.
71
+
72
+ ## Credential import
73
+
74
+ Use `interaction: "CredentialImport"` and a `Secret` field. The template has the
75
+ full manifest, `/api/me` verification, typed errors, and successful identity.
76
+ Adapt its token acquisition instructions and payload to the actual website.
77
+ Login contexts have no credential store access: return the verified payload;
78
+ Flare persists it. Do not call `flare.credential.replace` during login.
79
+
80
+ ## Password
81
+
82
+ Manifest method fragment:
83
+
84
+ ```json
85
+ {
86
+ "id": "password",
87
+ "interaction": "Password",
88
+ "title": "Sign in",
89
+ "fields": [
90
+ { "id": "username", "type": "Username", "label": "Username" },
91
+ { "id": "password", "type": "Password", "label": "Password" }
92
+ ]
93
+ }
94
+ ```
95
+
96
+ Service sketch (the three helpers are website-specific implementations):
97
+
98
+ ```js
99
+ const passwordService = {
100
+ async begin(request, context) {
101
+ const token = await exchangePassword(context.origin, request.values.username, request.values.password);
102
+ const profile = await verifiedProfile(context.origin, token);
103
+ return loginSuccess(context, profile, { accessToken: token });
104
+ },
105
+ };
106
+ ```
107
+
108
+ `exchangePassword` sends structured form/JSON/basic authentication as specified
109
+ by the website and checks the HTTP response. `verifiedProfile` maps the actual
110
+ authenticated identity. `loginSuccess` constructs the success shape above.
111
+ Register this as `login.password`. Report credential rejection with a typed
112
+ AuthenticationRequired or Validation error appropriate to that API.
113
+
114
+ ## Form
115
+
116
+ Use a form for a different fixed collection of fields, such as username + OTP:
117
+
118
+ ```json
119
+ {
120
+ "id": "otp",
121
+ "interaction": "Form",
122
+ "title": "Sign in with a code",
123
+ "fields": [
124
+ { "id": "username", "type": "Username", "label": "Username" },
125
+ { "id": "code", "type": "Otp", "label": "One-time code" }
126
+ ]
127
+ }
128
+ ```
129
+
130
+ ```js
131
+ const otpService = {
132
+ async begin(request, context) {
133
+ const credential = await verifyOneTimeCode(context.origin, request.values);
134
+ const profile = await verifiedProfile(context.origin, credential.accessToken);
135
+ return loginSuccess(context, profile, credential);
136
+ },
137
+ };
138
+ ```
139
+
140
+ Implement those website-specific helpers and register as `login.otp`. If the
141
+ website requires a preceding “send code” step, explain how the user obtains the
142
+ code outside this form or choose a supported browser flow. Do not invent dynamic
143
+ field updates, an extra resume call, or a pending-form transition.
144
+
145
+ ## OAuth
146
+
147
+ Declare `{ "id": "oauth", "interaction": "OAuth", "title": "Authorize" }`
148
+ without fields or Cookie config. Implement `login.oauth.begin` and `.resume`.
149
+ Add exact `authOrigins` for a separate authorization/token origin when needed;
150
+ this does not expand later account HTTP access.
151
+
152
+ 1. `begin` receives Host-owned `flowId`, `state`, `redirectUri`, and selected
153
+ origin. Use the supplied state and redirect URI. Register a client only if
154
+ the provider supports it, otherwise use the provider-approved configuration.
155
+ 2. Return an approved HTTPS browser URL and JSON `pendingPayload` containing
156
+ only the bounded data needed to resume (for example client ID and PKCE
157
+ verifier). The begin runtime closes; globals do not carry the flow forward.
158
+ 3. `resume` receives Host-validated callback parameters and persisted pending
159
+ payload. Handle the provider's denial/error response. Exchange the code,
160
+ verify the resulting account, and return success.
161
+
162
+ ```js
163
+ // begin result fragment; build authorizationUrl from the provider's contract.
164
+ const browserTransition = {
165
+ type: "externalBrowser",
166
+ url: authorizationUrl,
167
+ pendingPayload: { clientId, verifier },
168
+ };
169
+ ```
170
+
171
+ The Host owns callback validation, expiry, persistence, replay protection, and
172
+ relogin identity. It rejects a flow if the running package changed. The plugin
173
+ still implements the provider's token exchange and PKCE requirements; SHA-256
174
+ returns hex, so see [crypto encoding](runtime-http.md#credentials-crypto-and-locale).
175
+ Never hardcode a callback URI from a sample. See Pixelfed for dynamic client
176
+ registration, state propagation, code exchange, and profile verification.
177
+
178
+ ## WebCookie
179
+
180
+ Manifest method fragment:
181
+
182
+ ```json
183
+ {
184
+ "id": "browser",
185
+ "interaction": "WebCookie",
186
+ "title": "Sign in in the browser",
187
+ "cookie": {
188
+ "startUrl": "$accountOrigin/login",
189
+ "probes": [{
190
+ "url": "$accountOrigin/api/me",
191
+ "cookies": [{ "name": "session", "required": true }]
192
+ }]
193
+ }
194
+ }
195
+ ```
196
+
197
+ Service sketch; `fetchVerifiedCookieProfile`, `mapProfile`, and `loginSuccess`
198
+ must be implemented for the selected site's profile API and result shape:
199
+
200
+ ```js
201
+ const browserService = {
202
+ async begin(request, context) {
203
+ return { type: "webCookie", startUrl: context.origin + "/login" };
204
+ },
205
+ async check(request, context) {
206
+ const probeUrl = context.origin + "/api/me";
207
+ const cookies = request.cookies.cookies
208
+ .filter(cookie => cookie.sourceUrl === probeUrl)
209
+ .map(({ name, value }) => ({ name, value }));
210
+ const profile = await fetchVerifiedCookieProfile(probeUrl, cookies);
211
+ if (!profile) return { type: "pending" };
212
+ return loginSuccess(context, mapProfile(profile, context), { cookies });
213
+ },
214
+ };
215
+ ```
216
+
217
+ Register as `login.browser`. The returned start URL must exactly match the
218
+ resolved manifest start URL. The Host collects Cookies only for declared probes
219
+ and filters domain/path/HTTPS scope. `sourceUrl` identifies the probe: do not
220
+ combine Cookies from distinct origins merely because their names match. Use
221
+ structured `cookies` in the verification request, and retain only the values
222
+ appropriate for later account-origin requests. Required missing Cookies delay
223
+ checking; a present Cookie still needs server-side identity verification.
224
+
225
+ Return pending only for a verified “not authenticated yet” state. Map rate limits,
226
+ network failures, and malformed responses to their actual typed errors instead
227
+ of silently polling forever. The Host prevents overlapping checks and owns the
228
+ session's disposal; the plugin supplies no lifecycle hook.
229
+
230
+ ## Refresh and relogin
231
+
232
+ Refresh access tokens within an account method using the website's documented
233
+ flow and the account's allowed origin. After successful verification, atomically
234
+ replace the full credential payload, retaining required fields. Retry the
235
+ original operation only when safe (especially for writes); avoid unbounded loops.
236
+ An invalid/expired credential that cannot be refreshed should raise
237
+ AuthenticationRequired, which the account adapter routes to relogin. The Host
238
+ requires the same account identity on relogin; schema changes are described in
239
+ [publishing](publishing.md#updates-and-compatibility).
@@ -0,0 +1,88 @@
1
+ # Capability and UI contract
2
+
3
+ Each ID below has prefix `flare.datasource.` and suffix `/v1`; use the full ID
4
+ in the manifest and negotiated login result. JavaScript methods live under
5
+ `definePlugin({ capabilities: { <service>: ... } })`.
6
+
7
+ | ID middle | Service | Required operations | Additional operations | Host surface / dependency |
8
+ | --- | --- | --- | --- | --- |
9
+ | `timeline` | `timeline` | `page` | None | Static timelines, including at least one default |
10
+ | `search` | `search` | `posts`, `profiles`, `discoverPosts`, `discoverProfiles`, `discoverHashtags` | None | Search/discovery sections |
11
+ | `profile` | `profile` | `byId`, `byHandle` | `timeline`, `following`, `followers` | Profile lookup; tabs additionally require `timeline` and profileTabs |
12
+ | `post` | `post` | `detail` | `context`, `delete`, `mutate` | Internal post detail, thread context, and supported actions |
13
+ | `relation` | `relation` | `state`, `mutate` | None | Follow/block/mute controls; nonempty static relationActions |
14
+ | `compose` | `compose` | `publish` | None | Composer; requires composeDefaults and an account |
15
+ | `notification` | `notification` | At least one of `page`, `badge` | Both may be declared | `page` supplies the list and requires notificationFilters; badge alone is not a displayable login capability |
16
+ | `list` | `list` | `page`, `detail`, `create`, `update`, `delete`, `timeline`, `members`, `memberships`, `addMember`, `removeMember` | None | List browsing and management |
17
+ | `direct-message` | `directMessage` | `rooms`, `room`, `messages`, `send`, `delete`, `leave`, `create`, `badge`, `canSend` | None | DM rooms, conversation, and related account controls |
18
+ | `article` | `article` | `detail`, `comments` | None | Article detail when a post selects article navigation |
19
+ | `gallery` | `gallery` | `detail`, `comments`, `recommendations` | None | Gallery detail when a post selects gallery navigation |
20
+ | `tab-catalog` | `tabCatalog` | `page` | None | Dynamic timeline catalog; also needs timeline.page and a static/default timeline |
21
+
22
+ These sets come from the Host's `PluginAbiV1` contract, not from TypeScript
23
+ `Partial<Service>` declarations. Known capability operations must be implemented
24
+ and declared together. A website without a required operation may need a smaller
25
+ feature set; do not advertise a successful mutation that does nothing. A genuine
26
+ empty comments collection may return an empty ended page; an unsupported remote
27
+ operation should be explained and omitted where the Host allows omission.
28
+
29
+ ## Declaration, registration, negotiation
30
+
31
+ For the same timeline, these three shapes are intentionally different:
32
+
33
+ ```json
34
+ { "flare.datasource.timeline/v1": { "operations": { "page": { "directions": ["refresh", "older"] } } } }
35
+ ```
36
+
37
+ ```js
38
+ // Registration fragment: include inside the single definePlugin call.
39
+ const timelineService = {
40
+ async page(request, context) {
41
+ return loadTimeline(request.timelineId, request.page, context);
42
+ },
43
+ };
44
+ ```
45
+
46
+ ```json
47
+ { "flare.datasource.timeline/v1": ["page"] }
48
+ ```
49
+
50
+ The first is `platform.capabilities`, the second is `capabilities.timeline`
51
+ (with a website-specific `loadTimeline` implementation), and the third is the
52
+ login success's `capabilities`. On login, negotiate only declared, complete sets
53
+ that this account/instance supports. At least one displayable capability is
54
+ required: compose/relation alone and a notification badge alone do not qualify.
55
+ Guest access uses the enabled guest declaration and manifest capabilities instead
56
+ of account login negotiation. See [upgrade negotiation](publishing.md#updates-and-compatibility)
57
+ for how an existing account adopts newly declared operations after an update.
58
+
59
+ ## Entry points and actions
60
+
61
+ The Host builds UI from capabilities and static configuration. Implementing an
62
+ undeclared function does not add a UI entry. A static timeline ID is passed as
63
+ `request.timelineId`; profile tabs pass `request.tabId`. Names and parameters are
64
+ not network URLs unless your implementation explicitly interprets them that way.
65
+
66
+ Post actions additionally depend on their `ActionDescriptorV1`, enabled state,
67
+ account, and mutation capability. Set accurate `active`/`count` values even when
68
+ an action is disabled. The Host supplies layout and controls; plugins cannot
69
+ install arbitrary UI or arbitrary action kinds.
70
+
71
+ | `PostV1.detailType` | Click behavior | Required target |
72
+ | --- | --- | --- |
73
+ | Omitted or `post` | Internal post detail | post.detail |
74
+ | `article` | Internal article detail | article.detail + comments |
75
+ | `gallery` | Internal gallery detail | gallery.detail + comments + recommendations |
76
+ | `link` | Browser according to user settings | Absolute HTTPS `url`, no internal-detail capability needed |
77
+
78
+ Missing internal target capability makes that click unavailable; it does not
79
+ fall back to another detail type. `link` bypasses internal Deep Link matching.
80
+ Grid covers use the same article/gallery/link navigation; author/actions and
81
+ reposted targets retain their own behavior.
82
+
83
+ The login profile remains the current account's saved identity if profile lookup
84
+ is absent. It does not enable arbitrary profile pages, profile timelines, or
85
+ lookup methods. Use this for a minimal read-only feed such as the template.
86
+
87
+ See [recipes](recipes.md) for every service's inputs, result shape, and adaptation
88
+ points, and [the type declarations](../flare-plugin.d.ts) for full signatures.
@@ -0,0 +1,192 @@
1
+ # Manifest reference
2
+
3
+ `manifest.json` is UTF-8, strict JSON. Unknown fields, comments, and trailing
4
+ commas are rejected. Property names and enum spellings are case-sensitive.
5
+ Defaults below apply when a field is omitted; use omission for optional fields
6
+ rather than sending null for non-nullable values. Start from the complete
7
+ [template manifest](../assets/github-template/manifest.json).
8
+
9
+ ## Root object
10
+
11
+ | Field | Required / default | Contract |
12
+ | --- | --- | --- |
13
+ | `schemaVersion` | `1` | Manifest schema, currently exactly 1 |
14
+ | `apiVersion` | Required | Runtime ABI, currently exactly 1 |
15
+ | `id` | Required | Stable reverse-domain plugin ID; `[a-z][a-z0-9]*(?:\.[a-z][a-z0-9-]*)+` |
16
+ | `version` | Required | SemVer; use stable `major.minor.patch` for GitHub distribution |
17
+ | `defaultLocale` | `"en"` | BCP-47 locale tag |
18
+ | `name` | Required | Manifest text |
19
+ | `description` | Omitted | Manifest text |
20
+ | `permissions` | `{}` | Permission object below |
21
+ | `platform` | Required | Exactly one platform object |
22
+
23
+ Manifest text is either a nonblank string or `{ "key": "plugin.name", "fallback":
24
+ "Example" }`. Every localized key used by the manifest must exist in the default
25
+ locale catalog. Plain strings need no locale file. This encoding differs from
26
+ runtime `WireTextV1`; see [localization](recipes.md#localization).
27
+
28
+ ## Permissions
29
+
30
+ | Field | Default | Meaning |
31
+ | --- | --- | --- |
32
+ | `authOrigins` | `[]` | Up to 16 additional exact HTTPS origins for login, e.g. `https://login.example.org` |
33
+ | `mediaNetwork` | `false` | Allows automatic remote media loading, including avatars, HTML images, audio/video, and covers |
34
+
35
+ Auth origins have no paths, trailing slash, query, fragment, wildcard, or URL
36
+ credentials. Use normalized host casing and omit the default `:443` port.
37
+ They do not grant account/guest HTTP access to extra origins. Media permission
38
+ allows destinations across websites, including CDNs/federated servers; it does
39
+ not expand the HTTP API. See [origin rules](runtime-http.md#origins-and-permissions).
40
+
41
+ ## Platform object
42
+
43
+ | Field | Required / default | Contract |
44
+ | --- | --- | --- |
45
+ | `id` | Required | Stable platform ID, `[A-Za-z][A-Za-z0-9_.-]{0,63}`; avoid built-in and other plugin platform IDs |
46
+ | `name`, `description` | Name required | Manifest text |
47
+ | `detector` | Omitted | `{ "priority": 0 }`; priority -10000..10000, requires `detector.detect` |
48
+ | `loginMethods` | `[]` | Login declarations below; method IDs unique and at most one method per interaction kind |
49
+ | `capabilities` | `{}` | Capability ID → declaration; up to 32 entries |
50
+ | `timelines` | `[]` | Static timeline declarations; timeline capability requires at least one |
51
+ | `profileTabs` | `[]` | Profile tab declarations; requires `profile.timeline` |
52
+ | `deepLinks` | `[]` | Up to 64 structured rules; see [Deep Links](recipes.md#deep-links) |
53
+ | `composeDefaults` | Omitted | Required conservative defaults when declaring `compose.publish` |
54
+ | `guest` | Omitted | `{ "enabled": true }` enables guest datasource access; object defaults enabled to true |
55
+ | `credentialSchemaVersion` | `1` | Integer 1..1000000; changing it requires existing accounts to log in again |
56
+ | `timelineSchemaVersion` | `1` | Integer 1..1000000; changes timeline identity; see [upgrades](publishing.md#updates-and-compatibility) |
57
+
58
+ Other identifiers (method, field, timeline, tab, parameter, capture) generally use
59
+ `[A-Za-z][A-Za-z0-9_.-]{0,127}`. Parameter objects have at most 32 string values,
60
+ each at most 4096 characters. Plugin/platform IDs have their separate rules above.
61
+
62
+ ### Capabilities
63
+
64
+ ```json
65
+ {
66
+ "flare.datasource.timeline/v1": {
67
+ "operations": { "page": { "directions": ["refresh", "older"] } }
68
+ }
69
+ }
70
+ ```
71
+
72
+ An operation configuration is an object, not a function name or boolean.
73
+ `directions` defaults to `[]`, which the current loader treats as unrestricted;
74
+ declare the actually supported directions explicitly. Non-paging operations use
75
+ `{}`. The [capability matrix](capabilities.md) defines exact names and required
76
+ sets. Known capability declarations with missing methods/operations fail
77
+ installation; unknown capability IDs produce warnings and are ignored.
78
+
79
+ `notificationFilters` belongs on the notification capability declaration and
80
+ must be nonempty when `page` is declared. Values: `all`, `mention`, `comment`,
81
+ `like`. `relationActions` belongs on the relation declaration and must be
82
+ nonempty. Values: `follow`, `block`, `mute`. Both default to empty and are static
83
+ arrays; placing them on another capability is invalid.
84
+
85
+ ### Timelines and profile tabs
86
+
87
+ | Field | Timeline default | Profile-tab default |
88
+ | --- | --- | --- |
89
+ | `id`, `title` | Required ID and manifest text | Required ID and manifest text |
90
+ | `icon` | `"Home"` | `"Profile"` |
91
+ | `display` | `"List"` | `"List"` |
92
+ | `parameters` | `{}` | `{}` |
93
+ | `defaultForNewAccount` | `false` | Not a profile-tab field |
94
+
95
+ IDs must be unique within each list. At least one declared timeline must have
96
+ `defaultForNewAccount: true`. `display` is `List` or `Grid`, not lowercase.
97
+ `tabCatalog.page` still requires a static timeline and `timeline.page`.
98
+
99
+ Icons use these Host semantic names:
100
+ `Home`, `Notification`, `Search`, `Profile`, `Local`, `World`, `Featured`,
101
+ `Bookmark`, `Heart`, `List`, `Messages`, `Channel`, `Like`, `Repost`, `Reply`,
102
+ `Delete`, `Follow`, `Block`, `Mute`, `Info`. The platform icon itself is the
103
+ package's `assets/icon.<extension>` (PNG, JPEG, WebP, GIF or ICO), not one of these names.
104
+
105
+ #### Default and manually added tabs
106
+
107
+ When a new account with timeline access is added, Flare adds every timeline with
108
+ `defaultForNewAccount: true` in manifest order. The first default is also the
109
+ account's main timeline. Multiple defaults are supported; other static timelines
110
+ and dynamic `tabCatalog.page` entries remain available for manual addition.
111
+
112
+ Default tabs follow the same presentation convention as built-in platforms on
113
+ Android, iOS, native macOS and Compose Desktop:
114
+
115
+ | Where the tab appears | Title | Icon |
116
+ | --- | --- | --- |
117
+ | First automatically added default | Localized `platform.name` | Packaged platform icon |
118
+ | Other automatically added defaults | Declared timeline `title` | Packaged platform icon |
119
+ | Default timeline in the manual-add list | Declared timeline `title` | Packaged platform icon |
120
+ | Other timelines in the manual-add list | Declared timeline `title` | Declared timeline `icon` |
121
+
122
+ Keep functional titles such as Home, Discover or Bookmarks in the timeline
123
+ declarations; Flare supplies the platform name for the first automatic tab.
124
+ The declared semantic `icon` is not used for default timelines in these lists.
125
+ Timeline IDs, parameters and `List`/`Grid` presentation are unchanged by these rules.
126
+ Profile tabs continue to use their own declared titles and icons.
127
+
128
+ Automatic addition happens only when creating an account. Signing in again or
129
+ updating/reloading a plugin does not recreate existing tabs or overwrite saved
130
+ names, icons, ordering or user customizations. Updated declarations apply to
131
+ newly added tabs.
132
+
133
+ ### Login methods
134
+
135
+ Each method has required `id`, `interaction`, `title`; optional `description`;
136
+ `fields` defaults to `[]`; `cookie` is omitted except for WebCookie.
137
+
138
+ | Interaction | Required registration | Configuration |
139
+ | --- | --- | --- |
140
+ | `OAuth` | `login.<id>.begin`, `.resume` | No fields or Cookie configuration |
141
+ | `Password` | `login.<id>.begin` | At least one field |
142
+ | `CredentialImport` | `login.<id>.begin` | At least one field |
143
+ | `Form` | `login.<id>.begin` | At least one field |
144
+ | `WebCookie` | `login.<id>.begin`, `.check` | Cookie configuration; no fields |
145
+
146
+ Fields require `id`, `type`, `label`; `placeholder` is optional and `required`
147
+ defaults to true. Label/placeholder use manifest text. Field types are `Text`,
148
+ `Username`, `Password`, `Otp`, `Secret`. These select Host controls; the plugin
149
+ still validates credentials against the website. The form Host accepts at most
150
+ 32 submitted fields, each up to 16384 characters, with only declared field IDs.
151
+
152
+ Cookie configuration contains `startUrl` and `probes`. Each probe has `url` and
153
+ `cookies: [{ "name": "session", "required": true }]`. There must be 1..16
154
+ probes and 1..32 requirements per probe, without duplicate Cookie names within a
155
+ probe. Cookie start/probe URLs use an approved HTTPS origin, or the special
156
+ `$accountOrigin` prefix. That prefix is resolved in Cookie and Deep Link manifest
157
+ configuration; it is not a general string interpolation facility for plugin code.
158
+ See the [complete login recipes](authentication.md).
159
+
160
+ ### Compose defaults
161
+
162
+ | Field | Shape and defaults |
163
+ | --- | --- |
164
+ | `text` | Optional `{ "maxLength": 500 }`; limit 1..1000000 |
165
+ | `media` | Optional object; required `maxCount` (0..100), `maxBytes` (1..1073741824) |
166
+ | `media.minCountForNew` | Default 0, between 0 and maxCount |
167
+ | `media.supportedMimeTypes` | Default `[]`; up to 128 MIME patterns, including `image/*` |
168
+ | `media.altTextMaxLength` | Default 1500, range 0..100000 |
169
+ | `media.canSensitive` | Default true |
170
+ | `visibility` | Optional `{ "allowed": ["public"], "default": "public" }`; default must be allowed |
171
+ | `contentWarning` | Default false |
172
+ | `poll` | Optional `{ "maxOptions": 4 }`, range 2..100 |
173
+ | `language` | Optional `{ "maxCount": 1 }`, range 1..32 |
174
+
175
+ Visibility values are `public`, `unlisted`, `followers`, `direct`. Describe what
176
+ the website actually supports. Login may return an instance config. When both sides define a section, the Host
177
+ uses the stricter limits, intersects visibility/MIME constraints, and rejects
178
+ empty intersections. When only one side defines an optional section, that section
179
+ is retained; omission is not a prohibition. Content-warning support requires
180
+ both sides when both configs exist. Implement every resulting enabled field in
181
+ `compose.publish`; see [compose](recipes.md#compose).
182
+
183
+ ## Frequent invalid combinations
184
+
185
+ - Timeline capability without a static/default timeline.
186
+ - A profile tab without the profile timeline operation.
187
+ - Compose capability without compose defaults, or defaults without the capability.
188
+ - Only `search.posts` declared when the search Host interface requires all five operations.
189
+ - A login method ID that differs from the corresponding `login` object key.
190
+ - Localized manifest keys absent from the default catalog.
191
+ - Extra root properties such as `author`, `homepage`, `entry`, or a trust badge:
192
+ API v1 defines no such manifest fields; put human-facing information in README.
@@ -0,0 +1,111 @@
1
+ # GitHub publication and upgrades
2
+
3
+ GitHub is the recommended distribution path. Flare installs from a public
4
+ `https://github.com/OWNER/REPOSITORY` URL (optional `.git` suffix), with one plugin
5
+ at the repository root:
6
+
7
+ ```text
8
+ manifest.json
9
+ plugin.js
10
+ assets/icon.png
11
+ locales/en.json # optional; other BCP-47 catalogs as needed
12
+ README.md # author/user instructions, ignored by the importer
13
+ ```
14
+
15
+ The package must contain exactly one `assets/icon.<extension>`: `png`, `jpg`,
16
+ `jpeg`, `webp`, `gif`, or `ico`. Its extension must match its encoded contents.
17
+ The icon is kept unchanged, limited to 2 MiB and 1024×1024 pixels. SVG is not
18
+ supported. PNG remains the default in the templates.
19
+
20
+ Development files may coexist in the repository. Flare selects the plugin files;
21
+ it does not run a package manager, build script, or client-side bundler. Commit
22
+ the already-built classic script, including all runtime dependencies. A Flare
23
+ monorepo URL pointing to an example subdirectory is not a valid installation URL:
24
+ copy that example's contents into a repository you own first.
25
+
26
+ ## Prepare and publish
27
+
28
+ 1. Adapt the website endpoints and verify the advertised features. Replace the
29
+ template IDs, name, description, and placeholder icon before publication.
30
+ Keep real tokens, Cookies, passwords, and private fixtures out of Git history.
31
+ 2. Put the required files at root and check that a clean checkout contains them.
32
+ Include README instructions for supported website/instances, token acquisition
33
+ if relevant, features, permissions, and tests actually performed.
34
+ 3. Set a stable SemVer manifest version, for example `0.1.0`. Publish a tag
35
+ `0.1.0` or `v0.1.0` on the commit with those files. Removing the optional
36
+ **lowercase** `v` must exactly match manifest.version. Follow the same rule for
37
+ any permitted build metadata; plain major.minor.patch is the simplest choice.
38
+ 4. Share the repository URL. A GitHub Release page and uploaded `.fpp` asset are
39
+ not required. Creating/pushing the repository or tag follows the user's
40
+ authorization; a generated project alone is not permission to publish it.
41
+ 5. The user enters the repository URL in Flare plugin management and clicks
42
+ **Install**. Flare selects and downloads the latest stable version, then shows
43
+ source and permission review in the same dialog. Only confirmation installs
44
+ the plugin; cancellation discards the download. Errors stay in the dialog for
45
+ retry. Successful installs take effect immediately; verify the installed
46
+ tag/version and running behavior without restarting Flare.
47
+
48
+ Private repositories, arbitrary branch tracking, prerelease tags, automatic
49
+ installation, and build-on-device are unsupported. A public repo without a stable
50
+ version tag has no installable version, even if its default branch has valid files.
51
+
52
+ Flare resolves the latest stable tag to a commit SHA and downloads that source ZIP.
53
+ Archive checks also inspect ignored development files for unsafe paths/symlinks,
54
+ duplicates, multiple roots, and size limits. Do not commit node_modules, build
55
+ caches, or unrelated large assets. See [limits](troubleshooting.md#limits).
56
+
57
+ ## Updates and compatibility
58
+
59
+ Increment manifest.version and publish a new stable tag. Preserve plugin ID,
60
+ platform ID, and the GitHub repository identity for an in-place update. Flare
61
+ records repository ID, owner/name, tag, and SHA. A moved tag/source change or new
62
+ permission is surfaced in review; prefer a new tag over rewriting a published
63
+ one. Failure/cancellation leaves the installed package intact.
64
+
65
+ Opening management checks for updates using a five-minute metadata cache;
66
+ manual checking bypasses it. Discovery only offers an update; download/review/
67
+ confirmation are separate steps. GitHub metadata uses global proxy routing and
68
+ rate-limit handling, not plugin account credentials. Excessive tag lists produce
69
+ an error rather than an incomplete “latest version” answer.
70
+
71
+ Install, update, and uninstall take effect immediately. Installed plugins are always enabled. There are no plugin lifecycle/migration callbacks. Media
72
+ grants follow the running package; cached media retains plugin ownership and
73
+ rechecks that package's permission. Missing plugins cannot restore their media.
74
+ Uninstall records removal before account cleanup. Once that record is saved,
75
+ account-cleanup or final-index-write failures keep the plugin stopped across
76
+ restart; retry deletion from management to finish. Updating or reinstalling
77
+ cannot bypass incomplete removal. Unused package-file cleanup can be retried
78
+ separately from management after deletion completes.
79
+ Publishing controls follow the current account capabilities after replacement or
80
+ relogin, without requiring the account list to change.
81
+
82
+ | Change | Author consequence |
83
+ | --- | --- |
84
+ | Credential payload format becomes incompatible | Increment credentialSchemaVersion; existing accounts require relogin, with no plugin migration hook |
85
+ | Timeline IDs/parameters change incompatibly | Consider timelineSchemaVersion; it changes timeline identities and retires old specs. Explain that existing selections may need reconfiguration |
86
+ | Capabilities removed | Running capabilities are filtered by the new manifest |
87
+ | Operations newly declared compared with the account's saved manifest | Current Host admits those new operations without re-negotiating login; do not assume a new login will gate them |
88
+ | Previously declared operation was not negotiated for the account | It remains unavailable unless account negotiation changes |
89
+ | Package hash changes | Account adapter invalidates cached content when refreshing its persisted package snapshot |
90
+
91
+ Treat website/credential compatibility deliberately. A version bump alone does
92
+ not convert stored credentials. Document new permissions and user-visible changes.
93
+ The Host's own cache-database migration is separate from a plugin's versioning;
94
+ plugin authors do not control it.
95
+
96
+ ## Optional local installation
97
+
98
+ For offline development or local transfer, the skill includes Node's standard-
99
+ library-only packer. From this skill directory:
100
+
101
+ ```sh
102
+ node pack.mjs assets/github-template /tmp/example-feed-0.1.0.fpp
103
+ ```
104
+
105
+ For an adapted plugin repository, create a staging directory containing only
106
+ manifest.json, plugin.js, one supported assets/icon.<extension>, and optional locales/*.json, then pass
107
+ that directory to pack.mjs. The packer ignores only a root README.md; unlike
108
+ GitHub import it rejects other development files, including .git, tests, and type
109
+ declarations. Keep the output outside the input directory. Packaging success is
110
+ not full manifest/registration/behavior validation. Local packages are unverified
111
+ and still require review; installation takes effect immediately and no GitHub source/update tracking is recorded.