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,204 @@
1
+ # Flare Plugin SDK and skill
2
+
3
+ Use this directory as the `flare-plugin` skill to build a website adapter with an
4
+ AI coding agent, or read the same references directly. All material describes
5
+ Social Plugin API v1 in the accompanying Flare source. Use a Flare build that
6
+ includes this subsystem; this directory does not promise a minimum public app
7
+ release. The module targets Android, JVM Desktop, iOS, and native macOS, not Web.
8
+
9
+ ## Install the skill
10
+
11
+ This skill installs into your AI coding agent. The website adapter it helps you
12
+ create is installed separately into Flare; see [Start a plugin repository](#start-a-plugin-repository).
13
+ The source package is `feature/plugin/sdk/skill/flare-plugin/`. Copy that **entire
14
+ directory**, including references, types, assets, and examples. Copying only
15
+ `SKILL.md` loses the SDK. The parent `skill/` directory is just a container.
16
+
17
+ The package follows the [Agent Skills format](https://agentskills.io/specification):
18
+ the directory and frontmatter name are both `flare-plugin`, the entrypoint is
19
+ `SKILL.md`, and supporting files use relative links. It has no agent-specific
20
+ runtime dependency. Optional UI metadata such as `agents/openai.yaml` is not
21
+ needed to use it. Your agent still needs file access and the tools required by
22
+ your plugin project; Node is needed for the documented syntax and package checks.
23
+
24
+ ### Install from GitHub with Codex
25
+
26
+ The [development source](https://github.com/DimensionDev/Flare/tree/feature/plugin/feature/plugin/sdk/skill/flare-plugin)
27
+ is on the `feature/plugin` branch of `DimensionDev/Flare`. It is a development
28
+ branch, not a released SDK version. For reproducible installation, select the
29
+ full commit SHA containing the skill in GitHub and use it in place of the branch.
30
+
31
+ Ask Codex's built-in installer:
32
+
33
+ ```text
34
+ $skill-installer Install the flare-plugin skill from repository DimensionDev/Flare,
35
+ ref feature/plugin, path feature/plugin/sdk/skill/flare-plugin.
36
+ Copy the complete skill directory, including its supporting files.
37
+ ```
38
+
39
+ Give the repository, ref, and path separately: this ref contains a slash. If you
40
+ use the installer's Python CLI directly, the equivalent arguments are
41
+ `--repo DimensionDev/Flare --ref feature/plugin --path feature/plugin/sdk/skill/flare-plugin`.
42
+ Record the resolved commit SHA when you need to reproduce an installation. If a
43
+ skill already exists, follow [Update or remove](#update-or-remove) first.
44
+
45
+ ### Install from a local checkout
46
+
47
+ Check out a revision that contains this directory. For example, clone the
48
+ development branch into a new directory, then enter its repository root:
49
+
50
+ ```sh
51
+ git clone --branch feature/plugin --single-branch https://github.com/DimensionDev/Flare.git
52
+ cd Flare
53
+ ```
54
+
55
+ For an existing checkout, use your normal Git workflow to select the intended
56
+ revision before copying. No Flare build is needed to install the skill.
57
+
58
+ From the Flare repository root, choose **one** of the following first-install
59
+ commands. The existence check prevents copying into an already-installed skill.
60
+
61
+ Codex, for all local projects:
62
+
63
+ ```sh
64
+ mkdir -p "$HOME/.agents/skills"
65
+ if [ ! -e "$HOME/.agents/skills/flare-plugin" ] && [ ! -L "$HOME/.agents/skills/flare-plugin" ]; then
66
+ cp -R feature/plugin/sdk/skill/flare-plugin "$HOME/.agents/skills/flare-plugin"
67
+ else
68
+ echo "flare-plugin already exists; follow Update or remove before replacing it."
69
+ fi
70
+ ```
71
+
72
+ Claude Code, for all local projects:
73
+
74
+ ```sh
75
+ mkdir -p "$HOME/.claude/skills"
76
+ if [ ! -e "$HOME/.claude/skills/flare-plugin" ] && [ ! -L "$HOME/.claude/skills/flare-plugin" ]; then
77
+ cp -R feature/plugin/sdk/skill/flare-plugin "$HOME/.claude/skills/flare-plugin"
78
+ else
79
+ echo "flare-plugin already exists; follow Update or remove before replacing it."
80
+ fi
81
+ ```
82
+
83
+ For a project-only installation, copy the same `flare-plugin/` folder into the
84
+ **plugin project's** `.agents/skills/` (Codex) or `.claude/skills/` (Claude Code).
85
+ Use the plugin project's path, even when your source checkout is elsewhere.
86
+ Choose one scope per agent to avoid loading duplicate copies. The expected
87
+ layout is `<skills-directory>/flare-plugin/SKILL.md`, with resources beside it.
88
+
89
+ ### Verify discovery and invoke
90
+
91
+ 1. Check the installed folder contains `SKILL.md`, `flare-plugin.d.ts`,
92
+ `references/manifest.md`, `assets/github-template/plugin.js`, and
93
+ `examples/pixelfed/plugin.js`.
94
+ 2. In Codex CLI or its IDE extension, use `/skills` or type `$` and select
95
+ `flare-plugin`. If it is missing, restart Codex and check the installation
96
+ location. In Claude Code, type `/flare-plugin`; if a newly created skills
97
+ directory is not discovered, run `/reload-skills` or start a new session.
98
+ 3. Run a read-only check before creating a plugin:
99
+
100
+ ```text
101
+ Use the flare-plugin skill. Read its bundled references and template.
102
+ Explain the required files for a GitHub plugin and the available login methods,
103
+ and identify the template's illustrative endpoints. Do not create or publish files.
104
+ ```
105
+
106
+ Confirm the agent reads resources from the installed directory and identifies
107
+ `/api/me` and `/api/posts` as illustrative. This checks discovery and access,
108
+ not the correctness of a future website integration.
109
+
110
+ For actual work, prefix your request with `$flare-plugin` in Codex CLI/IDE or
111
+ `/flare-plugin` in Claude Code. Natural-language requests can also trigger the
112
+ skill when they match its description. See the current official
113
+ [Codex instructions](https://learn.chatgpt.com/docs/build-skills) and
114
+ [Claude Code instructions](https://code.claude.com/docs/en/skills) for discovery
115
+ locations, invocation, and client-specific behavior. These local installations
116
+ do not automatically install the skill in a remote or cloud agent environment.
117
+
118
+ ### Update or remove
119
+
120
+ To update, select the desired source revision and review its changes. Back up the
121
+ existing installed `flare-plugin` folder **outside all skill discovery directories**,
122
+ preserving any local edits, then replace it with the complete new folder. With
123
+ Codex's installer, move the old folder out before reinstalling; it does not
124
+ overwrite an existing installation. Avoid merging copies: removed reference
125
+ files would otherwise remain. Repeat the discovery check after replacement.
126
+
127
+ To remove, delete or move out only the installed `flare-plugin` folder from the
128
+ location you chose. Restart the agent if its menu still shows it. Updating or
129
+ removing this skill does not change generated plugin repositories or plugins
130
+ already installed in Flare.
131
+
132
+ ### Use without installing
133
+
134
+ An agent with access to this checkout can follow an explicit request to read
135
+ `feature/plugin/sdk/skill/flare-plugin/SKILL.md` and its referenced files. This
136
+ works for a single task; it does not register a discoverable skill. Keep the
137
+ entire folder accessible rather than pasting only the entrypoint into chat.
138
+
139
+ ## Use with an AI coding agent
140
+
141
+ Start with a request such as:
142
+
143
+ > Use the flare-plugin skill to create a plugin for [website / HTTPS API origin].
144
+ > Implement [features] using [login method]. Here are the API documentation and
145
+ > redacted example responses: [sources]. First map these features to Flare
146
+ > capabilities and list any missing API information. Then create a GitHub-ready
147
+ > repository, validate the available paths, and explain installation and any
148
+ > remaining integration work.
149
+
150
+ For an existing plugin:
151
+
152
+ > Use the flare-plugin skill to add [feature] to this repository. Preserve its
153
+ > plugin and platform IDs and existing features. Inspect the current manifest,
154
+ > implementation, and website API before editing. Update the capability mapping,
155
+ > documentation, and relevant checks, and explain the version/schema implications.
156
+
157
+ Provide API docs, authenticated-profile and content responses, paging examples,
158
+ and authentication details relevant to the requested feature. Remove real tokens,
159
+ passwords, Cookies, and private user content. Tell the agent which errors or edge
160
+ cases matter. It should identify gaps rather than invent website endpoints.
161
+
162
+ ## Start a plugin repository
163
+
164
+ 1. Copy the contents of [assets/github-template](assets/github-template/README.md)
165
+ to a new repository root. Its example website contract needs adaptation; no
166
+ demonstration server is required or provided.
167
+ 2. Choose stable plugin/platform IDs, replace the placeholder icon, and adapt
168
+ authentication and data mapping to your website. See [manifest](references/manifest.md)
169
+ and [authentication](references/authentication.md).
170
+ 3. Implement the required capability methods and map the responses into
171
+ [Wire data](references/wire-data.md). The starter uses token import and a
172
+ read-only timeline whose posts open their website URLs.
173
+ 4. Use [Desktop plugin development](references/testing.md#develop-locally-in-desktop)
174
+ to validate and reload a local directory. Follow the other testing checks, then
175
+ commit the ready-to-run `plugin.js` and publish a public GitHub repository with a stable version tag.
176
+ 5. In Flare's plugin management, enter `https://github.com/OWNER/REPO` and click
177
+ **Install**. Flare downloads the latest stable version and shows its source and
178
+ permissions in the same dialog. Confirm installation to use the plugin immediately.
179
+ Follow the plugin's README to select the platform, origin, and login method.
180
+
181
+ See [publishing](references/publishing.md) for exact tag rules and updates. Flare
182
+ reads the tagged repository root; a subdirectory in the Flare repository itself
183
+ is not a plugin installation URL. A GitHub Release or `.fpp` upload is unnecessary.
184
+
185
+ ## Reference and example index
186
+
187
+ | Need | Reference |
188
+ | --- | --- |
189
+ | Agent workflow | [SKILL.md](SKILL.md) |
190
+ | Boundary contract and registration | [ABI.md](ABI.md) |
191
+ | Exact JavaScript signatures and field comments | [flare-plugin.d.ts](flare-plugin.d.ts) |
192
+ | Manifest fields, defaults, enums, and validation | [Manifest](references/manifest.md) |
193
+ | All twelve capabilities, minimum methods, and UI requirements | [Capabilities](references/capabilities.md) |
194
+ | Platform engines, async completion, HTTP, permissions, credentials, and state | [Runtime and HTTP](references/runtime-http.md) |
195
+ | Detection and all five login methods | [Authentication](references/authentication.md) |
196
+ | DTO mapping, pagination, media, and opaque tokens | [Wire data](references/wire-data.md) |
197
+ | Capability implementations, guest, localization, and Deep Links | [Recipes](references/recipes.md) |
198
+ | Errors, resource limits, and recovery | [Troubleshooting](references/troubleshooting.md) |
199
+ | Author checks, Host tests, and acceptance evidence | [Testing](references/testing.md) |
200
+ | GitHub publication, upgrades, and optional local installation | [Publishing](references/publishing.md) |
201
+ | Complete website adapter | [Pixelfed](examples/pixelfed/README.md) |
202
+
203
+ Rules have one canonical reference; the skill links to it when needed. Recipe
204
+ fragments explain adaptation points and are not separate installable plugins.
@@ -0,0 +1,80 @@
1
+ ---
2
+ name: flare-plugin
3
+ description: Create, extend, debug, and prepare GitHub distribution for Flare Social Plugin API v1 plugins. Use when implementing a website adapter, editing a Flare plugin manifest or classic JavaScript entrypoint, adding login or datasource capabilities, or diagnosing Flare plugin installation and runtime errors.
4
+ ---
5
+
6
+ # Flare Plugin
7
+
8
+ Produce a website adapter that matches Flare's API v1 contract and can be installed
9
+ from a public GitHub repository. Use the user's chosen website and requested
10
+ features. This skill includes the SDK; resolve links relative to this file, not
11
+ to the user's plugin repository.
12
+
13
+ ## Establish the contract
14
+
15
+ 1. Inspect an existing plugin before changing it. For a new plugin, collect the
16
+ target HTTPS origin, requested features, login method, API documentation, and
17
+ representative redacted responses. Public API documentation may supply missing
18
+ information. Identify unresolved endpoint, authentication, identity, or paging
19
+ assumptions before implementing the affected feature.
20
+ 2. Read [manifest](references/manifest.md), [capabilities](references/capabilities.md),
21
+ and [runtime/HTTP](references/runtime-http.md). Map requested features to
22
+ capability IDs, their required operations, service method paths, and UI entry
23
+ points. Declare only supported behavior, including any required companion
24
+ operations. A function's TypeScript optionality does not relax manifest rules.
25
+ 3. Use [flare-plugin.d.ts](flare-plugin.d.ts) for exact signatures and [ABI.md](ABI.md)
26
+ for the boundary contract. Preserve API v1 behavior. If the target Flare build
27
+ has a different contract, establish its version before generating code; there
28
+ is no implied released-app minimum version in this skill.
29
+
30
+ ## Implement the requested adapter
31
+
32
+ Start a new repository from [the GitHub template](assets/github-template/README.md).
33
+ Copy the **contents** of `assets/github-template/` to the new repository root.
34
+ Its `/api/me` and `/api/posts` contract is illustrative, not a live service. Adapt
35
+ it from verified website evidence before presenting it as an integration.
36
+ Use [Pixelfed](examples/pixelfed/README.md) as a complete real-service example,
37
+ not as a universal API specification.
38
+
39
+ Read the references needed for the task:
40
+
41
+ | Task | Read |
42
+ | --- | --- |
43
+ | Detection, OAuth, password, token import, form, Cookie, credential refresh | [Authentication](references/authentication.md) |
44
+ | Map remote entities, HTML, media, identity, cursor pagination, opaque tokens | [Wire data](references/wire-data.md) |
45
+ | Implement any of the twelve capabilities, guest access, actions, Deep Links, localization | [Recipes](references/recipes.md) and the relevant [capability row](references/capabilities.md) |
46
+ | Diagnose a rejected package, missing UI, failed request, or retired runtime | [Troubleshooting and limits](references/troubleshooting.md) |
47
+ | Check the generated files and exercise behavior | [Testing](references/testing.md) |
48
+ | Prepare a GitHub repository, version tag, update, or schema change | [Publishing](references/publishing.md) |
49
+
50
+ Keep `plugin.js` a self-contained classic script and call `definePlugin` once.
51
+ Use `flare.http.request` with structured authorization/Cookies; retain credentials
52
+ through the Host account credential API. Map remote data explicitly into Wire
53
+ DTOs. Do not add fields to the ABI or assume browser/Node globals exist. Perform
54
+ Host calls inside registered methods, not during script initialization.
55
+
56
+ The manifest, registered methods, and login's negotiated capabilities must agree.
57
+ Keep account IDs stable, use the selected origin for authenticated identity, and
58
+ treat globals as disposable cache. Use typed `flare.error` results for expected
59
+ remote failures. Request media permission only when the adapter returns remote
60
+ media. Keep actual secrets out of source, examples, logs, and repository history.
61
+
62
+ ## Validate and deliver
63
+
64
+ Follow [testing](references/testing.md): check syntax, manifest/method agreement,
65
+ representative mapping and paging behavior, and available real Host validation.
66
+ For an existing plugin, recheck the behavior changed by the edit. State what ran
67
+ and what still needs the target website or Flare UI; syntax checks and mocked
68
+ responses do not prove live integration.
69
+
70
+ Prefer GitHub distribution: one plugin at repository root, an already-built
71
+ `plugin.js`, and a stable version tag matching `manifest.version`. Prepare files
72
+ and release instructions; create/push repositories, commits, or tags only within
73
+ the user's current authorization. See [publishing](references/publishing.md) for
74
+ immediate activation, uninstall cleanup, and the optional local `.fpp` path.
75
+
76
+ Deliver the complete repository files, feature-to-capability mapping, setup and
77
+ installation instructions, checks performed, and remaining assumptions. A new
78
+ adapter is ready for use only when its documented website contract and required
79
+ runtime paths have been exercised; otherwise identify the unfinished integration
80
+ steps precisely.
@@ -0,0 +1,71 @@
1
+ # Example Feed plugin template
2
+
3
+ This is a GitHub repository template for a token-based Flare API v1 adapter.
4
+ The website API below is an **illustrative contract**, not an available service.
5
+ Adapt the paths, authentication, and mapping to your website before publishing.
6
+ No demonstration server is included. Tests in Flare exercise this template with
7
+ fixture HTTP responses through the real QuickJS runtime.
8
+
9
+ ## Adapt the template
10
+
11
+ 1. Copy this directory's contents to your own repository root.
12
+ 2. Change `manifest.id`, `platform.id`, names, and description. Keep IDs stable
13
+ after publication. Replace the one-pixel placeholder PNG with your own icon.
14
+ 3. Adapt `plugin.js` using the Flare Plugin skill and your website's API evidence.
15
+ It currently verifies an imported bearer token with `/api/me`, then reads
16
+ `/api/posts`. It registers no detector, so choose the platform explicitly.
17
+ 4. Update this README with the actual website, token acquisition steps, supported
18
+ features, verification results, and remaining limitations.
19
+
20
+ The template requests no media permission and emits text-only posts. Every post
21
+ uses `detailType: "link"`, opening its HTTPS website URL. There is no arbitrary
22
+ profile lookup, internal post detail, write capability, or guest access. The
23
+ authenticated profile returned at login supplies the saved account identity.
24
+
25
+ ## Illustrative website API to replace
26
+
27
+ The selected origin is an absolute HTTPS origin such as `https://api.example.org`
28
+ with no path. Both requests use a structured bearer token. The imported token
29
+ is accepted only after the profile request succeeds.
30
+
31
+ `GET /api/me`:
32
+
33
+ ```json
34
+ { "id": "alice", "username": "alice", "name": "Alice" }
35
+ ```
36
+
37
+ `GET /api/posts?limit=20` and subsequent `&cursor=<encoded opaque cursor>`:
38
+
39
+ ```json
40
+ {
41
+ "items": [{
42
+ "id": "post-1",
43
+ "author": { "id": "alice", "username": "alice", "name": "Alice" },
44
+ "created_at": "2026-01-01T00:00:00Z",
45
+ "text": "A post from the website",
46
+ "url": "https://example.org/posts/post-1"
47
+ }],
48
+ "next_cursor": "page-2"
49
+ }
50
+ ```
51
+
52
+ `next_cursor` is omitted or null at the end. Empty pages may still contain a next
53
+ cursor. IDs are strings and stable across requests; request cursors never become
54
+ entity IDs. A refresh starts without the old cursor. Adapt these assumptions to
55
+ the actual API, including token expiry and rate-limit responses. The helper maps
56
+ 401/404/429 to typed errors, treats other HTTP failures as `Remote`, and parses
57
+ the response's string `body` explicitly.
58
+
59
+ ## Publish and install after adaptation
60
+
61
+ - Commit `manifest.json`, the self-contained `plugin.js`, one `assets/icon.png` (or JPEG/WebP/GIF/ICO), and
62
+ any `locales/*.json` at the root of a public GitHub repository.
63
+ - Publish a stable tag matching the manifest version, initially `v0.1.0`.
64
+ - In Flare plugin management, install `https://github.com/OWNER/REPO`, confirm the
65
+ review. The plugin becomes available immediately. A GitHub Release or `.fpp` asset is not required.
66
+ - Select your platform and HTTPS API origin, then import a real access token.
67
+ Verify that the saved account, Home timeline, pagination, and link clicks work.
68
+
69
+ Publish later changes under a new version tag. Keep tokens and Cookies out of
70
+ the repository. Describe separately which checks passed with fixtures and which
71
+ passed against the actual website and Flare UI.
@@ -0,0 +1,29 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "apiVersion": 1,
4
+ "id": "dev.example.flare.feed",
5
+ "version": "0.1.0",
6
+ "name": "Example Feed",
7
+ "description": "Adapt this token-based feed template to your website API",
8
+ "platform": {
9
+ "id": "ExampleFeed",
10
+ "name": "Example Feed",
11
+ "loginMethods": [{
12
+ "id": "token",
13
+ "interaction": "CredentialImport",
14
+ "title": "Import an access token",
15
+ "fields": [{ "id": "token", "type": "Secret", "label": "Access token" }]
16
+ }],
17
+ "capabilities": {
18
+ "flare.datasource.timeline/v1": {
19
+ "operations": { "page": { "directions": ["refresh", "older"] } }
20
+ }
21
+ },
22
+ "timelines": [{
23
+ "id": "home",
24
+ "title": "Home",
25
+ "icon": "Home",
26
+ "defaultForNewAccount": true
27
+ }]
28
+ }
29
+ }
@@ -0,0 +1,108 @@
1
+ // Adapt the illustrative website contract in README.md before publication.
2
+ definePlugin({
3
+ login: {
4
+ token: {
5
+ async begin(request, context) {
6
+ const token = request.values.token && request.values.token.trim();
7
+ if (!token) fail("Validation", "Enter an access token");
8
+ const profile = mapProfile(await requestJson(context.origin + "/api/me", token), context);
9
+ return {
10
+ type: "success",
11
+ value: {
12
+ accountId: profile.key.id,
13
+ origin: context.origin,
14
+ credential: { accessToken: token },
15
+ profile,
16
+ capabilities: { "flare.datasource.timeline/v1": ["page"] },
17
+ },
18
+ };
19
+ },
20
+ },
21
+ },
22
+ capabilities: {
23
+ timeline: {
24
+ async page(request, context) {
25
+ if (request.timelineId !== "home") fail("Unsupported", "Unknown timeline");
26
+ const credential = await flare.credential.read();
27
+ if (!credential || typeof credential !== "object" || Array.isArray(credential) ||
28
+ typeof credential.accessToken !== "string" || !credential.accessToken) {
29
+ fail("AuthenticationRequired", "Import your access token again");
30
+ }
31
+ let url = context.origin + "/api/posts?limit=" + request.page.limit;
32
+ if (request.page.direction !== "refresh" && request.page.cursor) {
33
+ url += "&cursor=" + encodeURIComponent(request.page.cursor);
34
+ }
35
+ const data = await requestJson(url, credential.accessToken);
36
+ if (!Array.isArray(data.items)) fail("InvalidResponse", "Expected a post array");
37
+ if (data.next_cursor != null && (typeof data.next_cursor !== "string" || !data.next_cursor)) {
38
+ fail("InvalidResponse", "Expected a non-empty cursor or null");
39
+ }
40
+ return {
41
+ items: data.items.map(post => ({
42
+ key: { id: requiredString(post && post.id), host: accountHost(context) },
43
+ author: mapProfile(post.author, context),
44
+ createdAt: requiredString(post.created_at),
45
+ content: { format: "plain", value: requiredString(post.text) },
46
+ detailType: "link",
47
+ url: requiredString(post.url),
48
+ })),
49
+ olderCursor: data.next_cursor,
50
+ endReached: data.next_cursor == null,
51
+ };
52
+ },
53
+ },
54
+ },
55
+ });
56
+
57
+ function accountHost(context) {
58
+ return context.origin.slice("https://".length);
59
+ }
60
+
61
+ function mapProfile(value, context) {
62
+ return {
63
+ key: { id: requiredString(value && value.id), host: accountHost(context) },
64
+ handle: requiredString(value.username),
65
+ displayName: requiredString(value.name),
66
+ };
67
+ }
68
+
69
+ function requiredString(value) {
70
+ if (typeof value !== "string" || !value.trim()) fail("InvalidResponse", "Missing required text field");
71
+ return value;
72
+ }
73
+
74
+ async function requestJson(url, token) {
75
+ let response;
76
+ try {
77
+ response = await flare.http.request({ url, authorization: { type: "bearer", token } });
78
+ } catch (error) {
79
+ if (error && error.code === "host.unavailable") fail("Network", "The request could not be completed");
80
+ throw error;
81
+ }
82
+ if (response.status === 401) fail("AuthenticationRequired", "The access token was rejected");
83
+ if (response.status === 404) fail("NotFound", "The requested resource was not found");
84
+ if (response.status === 429) {
85
+ const entry = Object.entries(response.headers).find(([name]) => name.toLowerCase() === "retry-after");
86
+ const value = entry && entry[1][0];
87
+ const seconds = value && /^\d+$/.test(value) ? Number(value) : undefined;
88
+ throw flare.error({
89
+ code: "RateLimited",
90
+ message: { value: "Try again later" },
91
+ retryAfterSeconds: typeof seconds === "number" && Number.isInteger(seconds) && seconds <= 604800 ? seconds : undefined,
92
+ });
93
+ }
94
+ if (response.status < 200 || response.status >= 300) fail("Remote", "The website returned an error");
95
+ let data;
96
+ try {
97
+ data = JSON.parse(response.body);
98
+ } catch (_) {
99
+ fail("InvalidResponse", "The website did not return JSON");
100
+ }
101
+ if (!data || typeof data !== "object" || Array.isArray(data)) fail("InvalidResponse", "Expected a JSON object");
102
+ return data;
103
+ }
104
+
105
+ /** @param {PluginErrorCodeV1} code @param {string} message @returns {never} */
106
+ function fail(code, message) {
107
+ throw flare.error({ code, message: { value: message } });
108
+ }
@@ -0,0 +1,78 @@
1
+ # Pixelfed sample plugin
2
+
3
+ This sample implements Pixelfed through Flare's Social Plugin API v1. It is a
4
+ self-contained classic script and has no npm or runtime dependencies.
5
+
6
+ Implemented features:
7
+
8
+ - exact Pixelfed detection;
9
+ - dynamic OAuth client registration and login;
10
+ - Home, Discover, Local, Federated, Bookmarks, and Favourites timelines;
11
+ - profile lookup, Posts/Gallery tabs, following/followers, and search;
12
+ - post detail, context, delete, favourite, repost, and bookmark actions;
13
+ - media upload and status publishing.
14
+
15
+ Guest mode, lists, direct messages, notifications, and plugin Deep Links are
16
+ intentionally omitted. The manifest exposes only the implemented capabilities
17
+ and configuration. Custom plugin UI is unavailable in API v1.
18
+
19
+ The manifest explicitly requests `mediaNetwork` so avatars and federated/CDN
20
+ images can load. Installation review discloses that media destinations may
21
+ receive this plugin's account data.
22
+
23
+ ## Develop locally in Desktop
24
+
25
+ In Settings > Plugins > Plugin development, select this directory and start
26
+ development. File edits are validated automatically; use **Reload plugin** to
27
+ apply them. See [local development](../../references/testing.md#develop-locally-in-desktop)
28
+ for account access, reload boundaries, and restoring the installed version.
29
+
30
+ ## Publish on GitHub and install
31
+
32
+ Copy the contents of this directory to the root of your own public GitHub
33
+ repository. Publish a stable tag `v0.1.0` matching `manifest.version`, then install
34
+ the repository URL from Flare's plugin management. It becomes available immediately. The script
35
+ is already built; a GitHub Release or uploaded `.fpp` asset is unnecessary.
36
+
37
+ For a separately maintained fork, choose your own plugin ID and a distinct
38
+ platform ID before release. The sample's `Pixelfed` platform ID cannot coexist
39
+ with another registered platform of the same name. Keep IDs stable after users
40
+ install the fork. See [GitHub publication and updates](../../references/publishing.md).
41
+
42
+ This example cannot be installed by passing the Flare monorepo URL or a GitHub
43
+ subdirectory URL: each installation repository must contain one root plugin.
44
+ Actual server behavior may differ by Pixelfed version; report the instance and
45
+ checks performed when claiming live compatibility.
46
+
47
+ ## Optional local package
48
+
49
+ From the skill directory (the installed `flare-plugin` directory, or
50
+ `feature/plugin/sdk/skill/flare-plugin` in a Flare checkout):
51
+
52
+ ```sh
53
+ node pack.mjs examples/pixelfed /tmp/pixelfed-0.1.0.fpp
54
+ ```
55
+
56
+ The output is deterministic: identical input bytes produce an identical `.fpp`.
57
+ Install it from Flare's Settings > Plugins page, then continue with
58
+ logging in. Local packages are unverified and should be reviewed before use.
59
+
60
+ The bundled icon comes from Pixelfed's official
61
+ [`brand-assets`](https://github.com/pixelfed/brand-assets) repository.
62
+
63
+ ## Read the implementation
64
+
65
+ - `manifest.json`: static capabilities, timeline/profile tabs, compose defaults,
66
+ localized labels, and explicit media permission.
67
+ - `plugin.js`: detection, OAuth registration/resume, structured HTTP, entity
68
+ mapping, cursor pagination, actions, and multipart publication.
69
+ - `locales/`: English and Simplified Chinese catalogs. The SDK guides are English;
70
+ these catalogs demonstrate plugin localization.
71
+
72
+ Use the [capability recipes](../../references/recipes.md) for features this sample
73
+ does not implement and [testing](../../references/testing.md) for evidence levels.
74
+
75
+ ## Optional real-instance smoke test
76
+
77
+ Set `PIXELFED_TEST_HOST` and `PIXELFED_TEST_TOKEN` when running the JVM tests.
78
+ The test remains read-only unless `PIXELFED_TEST_ALLOW_WRITE=1` is also set.
@@ -0,0 +1,24 @@
1
+ {
2
+ "error.authentication": "Sign in to Pixelfed again",
3
+ "error.invalidResponse": "Pixelfed returned an invalid response",
4
+ "error.network": "Unable to reach the Pixelfed instance",
5
+ "error.notFound": "The requested Pixelfed content was not found",
6
+ "error.rateLimited": "Pixelfed is rate limiting requests",
7
+ "error.remote": "Pixelfed rejected the request",
8
+ "error.unsupported": "This Pixelfed operation is not supported",
9
+ "error.validation": "Pixelfed rejected the request",
10
+ "login.oauth": "Continue with Pixelfed",
11
+ "login.oauth.description": "Authorize Flare in your browser",
12
+ "platform.description": "A federated image sharing platform",
13
+ "platform.name": "Pixelfed",
14
+ "plugin.description": "Pixelfed support implemented as a Flare social plugin",
15
+ "plugin.name": "Pixelfed Sample",
16
+ "profile.gallery": "Gallery",
17
+ "profile.posts": "Posts",
18
+ "timeline.bookmarks": "Bookmarks",
19
+ "timeline.discover": "Discover",
20
+ "timeline.favourites": "Favourites",
21
+ "timeline.federated": "Federated",
22
+ "timeline.home": "Home",
23
+ "timeline.local": "Local"
24
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "error.authentication": "请重新登录 Pixelfed",
3
+ "error.invalidResponse": "Pixelfed 返回了无效响应",
4
+ "error.network": "无法连接到 Pixelfed 实例",
5
+ "error.notFound": "找不到请求的 Pixelfed 内容",
6
+ "error.rateLimited": "Pixelfed 暂时限制了请求频率",
7
+ "error.remote": "Pixelfed 拒绝了请求",
8
+ "error.unsupported": "此 Pixelfed 操作不受支持",
9
+ "error.validation": "Pixelfed 拒绝了请求",
10
+ "login.oauth": "使用 Pixelfed 继续",
11
+ "login.oauth.description": "在浏览器中授权 Flare",
12
+ "platform.description": "联邦式图片分享平台",
13
+ "platform.name": "Pixelfed",
14
+ "plugin.description": "通过 Flare Social 插件实现的 Pixelfed 支持",
15
+ "plugin.name": "Pixelfed 示例插件",
16
+ "profile.gallery": "相册",
17
+ "profile.posts": "帖子",
18
+ "timeline.bookmarks": "书签",
19
+ "timeline.discover": "发现",
20
+ "timeline.favourites": "喜欢",
21
+ "timeline.federated": "联邦",
22
+ "timeline.home": "首页",
23
+ "timeline.local": "本地"
24
+ }