-
-
Notifications
You must be signed in to change notification settings - Fork 98
Add agent skill for creating web framework integration packages #672
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
be21695
Define create-integration-package skill
2chanhaeng 4fbc643
Add lint and format checks step to create-integration-package skill
2chanhaeng c0ef070
Remove unnecessary .gitignore guidance from create-integration-packag…
2chanhaeng 1e41bbd
Update SKILL.md to include testing prerequisites and linting instruct…
2chanhaeng 2ab0be6
Update SKILL.md to publish package on both JSR and NPM
2chanhaeng 2f31207
Minor fix `/create-integration-package` skill
2chanhaeng 32536fc
Fix delete method in PostStore to correctly filter timeline by URL href
2chanhaeng 57da48a
Fix frameworkDescription property name from 'name' to 'label'
2chanhaeng 9fe141b
Fix module/exports fields in package template
2chanhaeng 97f58f4
Remove NestJS peer from package template
2chanhaeng 98eb652
Add imports section to deno.jsonc template
2chanhaeng c8092e0
Fix hardcoded @fedify/nuxt in README template
2chanhaeng bc82931
Fix optional param with default value syntax
2chanhaeng 645230f
Update testing instructions in SKILL.md to include unit tests and usa…
2chanhaeng b0d3dce
Fix typos in create-integration-package skill templates
2chanhaeng e7d4160
Fix textarea resize to vertical per design spec
2chanhaeng 2f8ff99
Lint
2chanhaeng 64d9ffe
Separate `create-integration-package` skill
2chanhaeng 87e1fb6
Fix awkward wording in example README
2chanhaeng 36e1f9f
Simplify redundant null check in example
2chanhaeng 0fba445
Use type-only import for WebFrameworkDescription
2chanhaeng 0316e49
Fix `mise test:example` to `mise test:examples`
2chanhaeng c42ca81
Export `fedifyMiddleware` as default and rename in README.md in skill…
2chanhaeng 7322cb4
Import stores from store.ts to federation.ts
2chanhaeng File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,129 @@ | ||
| --- | ||
| name: add-to-fedify-init | ||
| description: >- | ||
| This skill is used to add an integration package to the @fedify/init | ||
| package so that users can select the new framework via the `fedify init` | ||
| command, and to test it with `mise test:init`. | ||
| argument-hint: "Provide the name of the web framework to register in @fedify/init." | ||
| --- | ||
|
|
||
| <!-- deno-fmt-ignore-file --> | ||
|
|
||
| Adding an integration package to `@fedify/init` | ||
| =============================================== | ||
|
|
||
| Follow these steps in order to register the integration package in | ||
| `@fedify/init` and verify it works. | ||
|
|
||
| 1. Add to `@fedify/init` | ||
| 2. Test with `mise test:init` | ||
| 3. Lint, format, and final checks | ||
|
|
||
|
|
||
| Add to `@fedify/init` | ||
| --------------------- | ||
|
|
||
| Add the new package to the `@fedify/init` package so users can select the | ||
| new framework via the `fedify init` command. Follow these steps. | ||
|
|
||
| Steps may require code modifications not explicitly listed. For example, | ||
| if the new package needs specific configuration, utility functions in | ||
| `packages/init/src/webframeworks/utils.ts` may need updating. Make | ||
| modifications consistent with the existing code style and context. | ||
|
|
||
| ### Write the `WebFrameworkDescription` object | ||
|
|
||
| Create a `packages/init/src/webframeworks/framework.ts` file and write the | ||
| `WebFrameworkDescription` object, referring to <init/framework.ts>. Check | ||
| the specifications in the comments in `packages/init/src/types.ts` for | ||
| details. | ||
|
|
||
| ### Add to the `WEB_FRAMEWORK` array | ||
|
|
||
| Add the new framework name to the end of the `WEB_FRAMEWORK` array in | ||
| `packages/init/src/const.ts`. | ||
|
|
||
| ~~~~ typescript | ||
| export const WEB_FRAMEWORK = [ | ||
| // ... other frameworks | ||
| "framework", // Fill with the framework name | ||
| ]; | ||
| ~~~~ | ||
|
|
||
| ### Add to the `webFrameworks` object | ||
|
|
||
| Add the new `WebFrameworkDescription` object in alphabetical order to the | ||
| `webFrameworks` object in `packages/init/src/webframeworks/mod.ts`. | ||
|
|
||
| ~~~~ typescript | ||
| // packages/init/src/webframeworks/mod.ts | ||
|
|
||
| // ... other imports | ||
| import framework from "./framework.ts"; // Fill with the framework name | ||
|
|
||
| const webFrameworks: Record<string, WebFrameworkDescription> = { | ||
| // ... other frameworks | ||
| framework, // Fill with the framework name | ||
| }; | ||
| ~~~~ | ||
|
|
||
| ### Add templates in `packages/init/src/templates/framework/` | ||
|
|
||
| If additional files need to be generated, add template files under the | ||
| `packages/init/src/templates/framework/` directory. Template files must | ||
| end with the `.tpl` extension appended to their base name. Then, in | ||
| `packages/init/src/webframeworks/framework.ts`, load the templates using | ||
| the `readTemplate` function defined in `packages/init/src/lib.ts` and add | ||
| them to the `WebFrameworkDescription.init().files` object. | ||
|
|
||
|
|
||
| Test with `mise test:init` | ||
| -------------------------- | ||
|
|
||
| Run `mise test:init` to verify that the new package is generated and runs | ||
| correctly. If a test fails, the output and error file paths are printed; | ||
| read them to diagnose the issue. | ||
|
|
||
| Running `mise test:init` without arguments tests all option combinations | ||
| and can take a very long time. Use appropriate options to narrow the test | ||
| scope. | ||
|
|
||
| Immediately remove test paths after completing the tests and analyzing any | ||
| resulting errors. | ||
|
|
||
| At a minimum, test the following three combinations. | ||
|
|
||
| - `mise test:init -w framework -m in-process -k in-memory --no-dry-run`: | ||
| Tests the new framework with the in-memory KV store and in-process message | ||
| queue, which are the most basic options. This combination verify that the | ||
| newly created package can be used without issues by minimizing dependencies | ||
| on other environments. | ||
| - `mise test:init -w framework`: Tests all package manager, KV store, | ||
| and message queue combinations with the framework selected. If a | ||
| required database is not installed or running, this combinations are | ||
| useless. Therefore, if the test output indicates that the databases are | ||
| not running, don't use this combination ever again for the session. | ||
| Instead, use the previous one or the next one. | ||
| - `mise test:init -m in-process -k in-memory --no-dry-run`: Fixes the | ||
| KV store and message queue and tests all web framework and package | ||
| manager combinations. This test is mandatory if you modified logic | ||
| beyond just writing the `WebFrameworkDescription` object. | ||
|
|
||
| For details on options, run `mise test:init --help`. | ||
|
|
||
| Some frameworks or combinations may be untestable. Analyze the test | ||
| results; if there are impossible combinations, identify the reason and add | ||
| the combination and reason as a key-value pair to the | ||
| `BANNED_LOOKUP_REASONS` object in | ||
| `packages/init/src/test/lookup.ts`. | ||
|
|
||
|
|
||
| Lint, format, and final checks | ||
| ------------------------------ | ||
|
|
||
| Add keywords related to the framework in `.hongdown.toml` and `cspell.json` in | ||
| root path. | ||
|
|
||
| After implementation, run `mise run fmt && mise check`. | ||
| If there are lint or format errors, fix them and run the command again until | ||
| there are no errors. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| // packages/init/src/webframeworks/프레임워크.ts | ||
| // The import paths are written based on the files in | ||
| // `packages/init/src/webframeworks/` where the actual files must exist, | ||
| // so do not modify them unless necessary. | ||
|
|
||
| import deps from "../json/deps.json" with { type: "json" }; | ||
| import type { WebFrameworkDescription } from "../types.ts"; | ||
| import { defaultDenoDependencies, defaultDevDependencies } from "./const.ts"; | ||
| import { getInstruction } from "./utils.ts"; | ||
|
|
||
| const frameworkDescription: WebFrameworkDescription = { | ||
| label: "프레임워크", // Fill 프레임워크 with the official framework name | ||
| packageManagers: [ | ||
| // List the package managers that support this framework, | ||
| // the list should be a subset of `PACKAGE_MANAGER` from `../const.ts`. | ||
| // If the framework is compatible with all package managers, | ||
| // you can just use `packageManagers: PACKAGE_MANAGER`. | ||
| ], | ||
| defaultPort: 0, // Fill in the default port of the framework | ||
| init: ({ | ||
| // Destructure necessary parameters from the argument | ||
| packageManager: pm, | ||
| }) => ({ | ||
| command: [ | ||
| // Optional shell command to run before scaffolding e.g., `create-next-app`. | ||
| // Split the command into an array of command and arguments, | ||
| // e.g., `["npx", "create-next-app@latest"]`. | ||
| ], | ||
| dependencies: pm === "deno" | ||
| ? { | ||
| // Use `deps.json` for version numbers, | ||
| // e.g., `"@fedify/프레임워크": deps["@fedify/프레임워크"]`. | ||
| ...defaultDenoDependencies, | ||
| } | ||
| : { | ||
| // Use `deps.json` for version numbers, | ||
| // e.g., `"@fedify/프레임워크": deps["@fedify/프레임워크"]`. | ||
| }, | ||
| devDependencies: { | ||
| // Use `deps.json` for version numbers, | ||
| // e.g., `"@fedify/프레임워크": deps["@fedify/프레임워크"]`. | ||
| ...defaultDevDependencies, | ||
| }, | ||
| federationFile: "**/federation.ts", | ||
| loggingFile: "**/logging.ts", | ||
| tasks: { | ||
| // If `command` create a project with `tasks` in `deno.json` (or `script`s in | ||
| // `package.json`) to run application, this could be unnecessary. | ||
| // In the tasks of the finally generated application, at least include | ||
| // a `dev` task to run the development server. `dev` task is used by | ||
| // `mise test:init` to run tests against the generated project. | ||
| // For Node.js/Bun, `lint: "eslint ."` is needed. | ||
| }, | ||
| instruction: getInstruction(pm, 0 /* Replace with default port */), | ||
| }), | ||
| }; | ||
|
|
||
| export default frameworkDescription; | ||
191 changes: 191 additions & 0 deletions
191
.agents/skills/create-example-app-with-integration/ARCHITECTURE.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,191 @@ | ||
| <!-- deno-fmt-ignore-file --> | ||
|
|
||
| Fedify example architecture | ||
| =========================== | ||
|
|
||
| This document defines the shared architecture for Fedify example applications. | ||
| Every example should follow these conventions regardless of the web framework | ||
| used, so that learners can compare examples and transfer knowledge between them. | ||
|
|
||
|
|
||
| Middleware integration | ||
| ---------------------- | ||
|
|
||
| Every Fedify framework adapter exposes a middleware or hook function that | ||
| intercepts incoming requests. Register this middleware at the top level of | ||
| the server so that it runs before any application routes. | ||
|
|
||
| The middleware inspects the `Accept` and `Content-Type` headers. Requests | ||
| carrying ActivityPub media types (`application/activity+json`, | ||
| `application/ld+json`, etc.) or targeting well-known federation endpoints | ||
| are forwarded to the `Federation` instance. All other requests fall through | ||
| to the application's own routes. | ||
|
|
||
| The specific API differs, but the role is identical: delegate federation | ||
| traffic to Fedify, let everything else pass through. | ||
|
|
||
|
|
||
| Reverse proxy support | ||
| --------------------- | ||
|
|
||
| If needed, wrap the middleware (or the request handler it receives) with | ||
| `getXForwardedRequest` from the `x-forwarded-fetch` package. This rewrites | ||
| the request URL to respect `X-Forwarded-Host` and related headers, which is | ||
| required when the server runs behind a tunneling tool or reverse proxy during | ||
| local development. Apply this wrapping at the same level as the Fedify | ||
| middleware registration, before any routing logic executes. | ||
|
|
||
|
|
||
| Routing | ||
| ------- | ||
|
|
||
| ### `GET /` | ||
|
|
||
| The main page. Contains the following sections: | ||
|
|
||
| **Search** | ||
|
|
||
| A text input for searching fediverse accounts by handle. The client | ||
| debounces input with a 300ms delay, then sends a `GET` request with the | ||
| handle as a URL query parameter (e.g. `/?q=@user@example.com`). The server | ||
| resolves the handle using Fedify's `lookupObject` and returns the result. | ||
| The result shows: profile image, display name, handle, and a follow button. | ||
| If the local actor already follows the target, show an unfollow button | ||
| instead. | ||
|
|
||
| **User info** | ||
|
|
||
| Displays the local actor's profile. Because this is a demo there is exactly | ||
| one actor, `@demo`. | ||
|
|
||
| - Profile image: `/demo-profile.png` | ||
| - Name: `"Fedify Demo"` | ||
| - Handle: `@demo` | ||
| - Summary: `"This is a Fedify Demo account."` | ||
|
|
||
| **Following** | ||
|
|
||
| Lists accounts the local actor follows. Shows the total count and, for | ||
| each account: profile image, display name, handle, and an unfollow button. | ||
|
|
||
| **Followers** | ||
|
|
||
| Lists accounts that follow the local actor. Shows the total count and, for | ||
| each account: profile image, display name, and handle. | ||
|
|
||
| The following and followers sections update in real time via SSE (see below). | ||
|
|
||
| **Compose** | ||
|
|
||
| A text area and a submit button for writing a new post. On submission the | ||
| server creates a `Note`, stores it in `postStore`, wraps it in a `Create` | ||
| activity, and sends it to followers. If sending fails, the post is removed | ||
| from the store. | ||
|
|
||
| **Posts** | ||
|
|
||
| Lists all posts by the local actor in reverse chronological order. Each | ||
| entry shows the post content, published timestamp, and a link to the | ||
| single post detail page (`/users/{identifier}/posts/{id}`). | ||
|
|
||
| ### `GET /users/{identifier}` | ||
|
|
||
| Actor profile page. Shares its path with the Fedify actor dispatcher. | ||
| When a federation peer requests this URL with an ActivityPub media type, the | ||
| middleware handles it. Otherwise the request falls through to this route, | ||
| which renders an HTML page showing: | ||
|
|
||
| - Profile image | ||
| - Name | ||
| - Handle | ||
| - Summary | ||
| - Following count | ||
| - Followers count | ||
|
|
||
| ### `GET /users/{identifier}/posts/{id}` | ||
|
|
||
| Single post detail page. Shares its path with the Fedify `Note` object | ||
| dispatcher. Same content-negotiation fallback as the actor profile: the | ||
| middleware serves ActivityPub JSON to federation peers, and this route | ||
| renders HTML for browsers. Shows: | ||
|
|
||
| - Author profile (same layout as the actor profile page) | ||
| - Post content | ||
| - Published timestamp | ||
|
|
||
| ### `POST /post` | ||
|
|
||
| Accepts post content from the compose form, creates a `Note`, stores it in | ||
| `postStore`, wraps it in a `Create` activity, and sends it to followers. | ||
| If sending fails, the post is removed from the store. Redirects back to | ||
| `/` on completion. | ||
|
|
||
| ### `POST /follow` | ||
|
|
||
| Accepts a target actor URI, sends a `Follow` activity from the local actor, | ||
| and stores the relationship locally. | ||
|
|
||
| ### `POST /unfollow` | ||
|
|
||
| Accepts a target actor URI, sends an `Undo(Follow)` activity, and removes | ||
| the relationship locally. | ||
|
|
||
| ### `GET /events` | ||
|
|
||
| SSE endpoint. See the SSE section below. | ||
|
|
||
|
|
||
| Server-sent events | ||
| ------------------ | ||
|
|
||
| The `/events` endpoint keeps an open SSE connection to the client. | ||
| When the following or followers list changes (a follow is accepted, a | ||
| remote follow arrives, an unfollow occurs, etc.), the server pushes an | ||
| event so the page can update without a full reload. | ||
|
|
||
| The server maintains a set of active SSE connections. Whenever the | ||
| follower or following store is mutated — inside inbox listeners or after a | ||
| local follow/unfollow request — it broadcasts an event to every open | ||
| connection. | ||
|
|
||
| The client listens on an `EventSource` and replaces the relevant DOM | ||
| section with the received data. | ||
|
|
||
|
|
||
| Server-side data access | ||
| ----------------------- | ||
|
|
||
| Use Fedify's `RequestContext` to bridge between the framework routing layer | ||
| and the federation layer. Obtain a context by calling | ||
| `federation.createContext(request, contextData)` inside a route handler. | ||
| Through this context, routes can look up actors, resolve object URIs, and | ||
| invoke `sendActivity` without coupling to Fedify internals. | ||
|
|
||
| Avoid accessing the data stores directly from route handlers when a | ||
| `RequestContext` method exists for the same purpose. This keeps the | ||
| routing layer thin and ensures that Fedify's internal bookkeeping (key | ||
| resolution, URI canonicalization, etc.) is applied consistently. | ||
|
|
||
|
|
||
| Federation | ||
| ---------- | ||
|
|
||
| Use `src/federation.ts`. | ||
|
|
||
|
|
||
| Storing | ||
| ------- | ||
|
|
||
| Use `src/store.ts` and the provided in-memory stores. | ||
|
|
||
|
|
||
| View rendering | ||
| -------------- | ||
|
|
||
| See `DESIGN.md`. | ||
|
|
||
|
|
||
| Logging | ||
| ------- | ||
|
|
||
| Use `@logtape/logtape` and `src/logging.ts`. |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.