Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/skill-family-better-near-auth.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"better-near-auth": minor
---

Upgrade all six skills with "the tasks you will actually be given" walkthroughs and grounded refusal-word → action tables (NEP-413 verify, nonce/replay, relay limits, sub-account rollback, session and wallet-connection failures), and extract the client skill's 50-line action tables into `references/client-actions.md`.
5 changes: 5 additions & 0 deletions .changeset/skill-family-content.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"everything-dev": minor
---

Add task-shaped skills `talk-to-the-app` (MCP/REST/RPC surfaces, API-key auth, discovery) and `add-a-route` (one complete contract → Effect handler → UI client → route → publish slice), and rework the public `/skill.md` into an entry-point router over the full 20-skill family with shared facts, pairings, and rules to work by. Every existing skill gains "the tasks you will actually be given" walkthroughs and grounded error-word → action tables.
5 changes: 5 additions & 0 deletions .changeset/skill-family-ui-surface.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"every-plugin": minor
---

Serve the platform skill family from the core UI build — `node_modules/{everything-dev,every-plugin,better-near-auth}/skills` copy into `dist/skills/<package>/` and are reachable at `/skills/<package>/<skill>/SKILL.md` (with the rest of the family listed from `/skill.md`). Children get the same copies from the published npm tarballs.
6 changes: 6 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,9 @@ tanstackIntent:
- id: "every-plugin#plugin-testing"
run: "pnpm dlx @tanstack/intent@latest load every-plugin#plugin-testing"
for: "Test every-plugin modules with vitest and the plugin runtime. Use when writing or modifying plugin tests under plugins/*/src/__tests__/ or plugins/*/tests/."
- id: "everything-dev#add-a-route"
run: "pnpm dlx @tanstack/intent@latest load everything-dev#add-a-route"
for: "Add one API endpoint end to end in everything.dev — contract route with Zod schemas, Effect-native handler in the plugin router, UI call via useApiClient, route file rendering the data, typecheck, publish. Use when adding an API endpoint, wiring a new UI page to app data, or debugging the contract/handler/client chain."
- id: "everything-dev#api-and-auth"
run: "pnpm dlx @tanstack/intent@latest load everything-dev#api-and-auth"
for: "API architecture, oRPC contracts, auth middleware, plugin-client composition, session handling, and client-side auth. Use when adding API routes, creating middleware, calling other plugins in-process, or integrating auth in routes and UI."
Expand Down Expand Up @@ -217,6 +220,9 @@ tanstackIntent:
- id: "everything-dev#super-app"
run: "pnpm dlx @tanstack/intent@latest load everything-dev#super-app"
for: "Build shared-host, shared-API super apps with tenant-specific UI composition. Use when setting up a base runtime plus custom tenant apps, configuring fixed-core multi-tenancy, reasoning about extends-based runtime lineage, or deciding what tenants can override today."
- id: "everything-dev#talk-to-the-app"
run: "pnpm dlx @tanstack/intent@latest load everything-dev#talk-to-the-app"
for: "Work a running everything.dev app over its API without cloning it — MCP tools, REST/OpenAPI, oRPC RPC, plugin RPC, API-key authentication, and discovery endpoints. Use when an agent needs to read or write app data (registry, proposals, votes, auth session, AI chat) over HTTP, choose between MCP/REST/RPC surfaces, or debug authentication and refused calls."
- id: "everything-dev#ui-integration"
run: "pnpm dlx @tanstack/intent@latest load everything-dev#ui-integration"
for: "Route creation, API client usage, auth client, SSR hydration, sidebar system, and the @/app module surface. Use when adding new UI routes, fetching data from the API, implementing auth flows, or customizing sidebar navigation."
Expand Down
4 changes: 4 additions & 0 deletions packages/better-near-auth/skills/_artifacts/skill_tree.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,12 @@ skills:
domain: 'client'
path: 'skills/client/SKILL.md'
description: 'Set up the siwnClient plugin, configure wallet connection, use authClient.near actions for sign-in, account management, delegate action building, and relay submission'
references:
- 'references/client-actions.md'
sources:
- 'elliotBraem/better-near-auth:src/client.ts'
- 'elliotBraem/better-near-auth:src/types.ts'
- 'elliotBraem/better-near-auth:src/index.ts'
- 'elliotBraem/better-near-auth:README.md'
- 'elliotBraem/better-near-auth:LLM.txt'
- name: 'TanStack Router integration'
Expand Down Expand Up @@ -103,3 +106,4 @@ skills:
- 'elliotBraem/better-near-auth:examples/auth.everything.dev/api/src/index.ts'
- 'elliotBraem/better-near-auth:examples/auth.everything.dev/api/src/lib/auth.ts'
- 'elliotBraem/better-near-auth:examples/auth.everything.dev/api/src/lib/context.ts'
- 'elliotBraem/better-near-auth:src/index.ts'
23 changes: 23 additions & 0 deletions packages/better-near-auth/skills/auth-plugin/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ sources:
- "elliotBraem/better-near-auth:examples/auth.everything.dev/api/src/index.ts"
- "elliotBraem/better-near-auth:examples/auth.everything.dev/api/src/lib/auth.ts"
- "elliotBraem/better-near-auth:examples/auth.everything.dev/api/src/lib/context.ts"
- "elliotBraem/better-near-auth:src/index.ts"
---

# Better-Near-Auth — Auth Plugin (everything.dev)
Expand Down Expand Up @@ -348,3 +349,25 @@ interface AuthRequestContext {

- **Cause**: Only scalar fields survive JSON round-trip (`bos.config.json`). Function-typed fields (`extendTx`, `onCreated`, `onRollback`, dynamic `init.args`) and binary data (`deploy.wasm`) cannot be expressed in a config file.
- **Fix**: Use `bos.config.json` for `parentAccount`, `parentHasFullAccess`, `minDeposit`, `deploy.fromPublished`, static `init.args`. Configure `extendTx` / `onCreated` / `onRollback` / dynamic `init.args` / raw wasm through `siwn()` directly on the server instead of through the plugin.

## The tasks you will actually be given

**"Add NEAR sign-in to my everything-dev app and protect the settings pages."**
Register auth under `app.auth` in `bos.config.json` with `variables.siwn.recipients.{mainnet,testnet}`, keep `ui/src/lib/auth.ts` as the re-export of `everything-dev/ui/auth` (`createAuthClient`, `useAuthClient`, `sessionQueryOptions`), and add an `_authenticated.tsx` layout that runs `sessionQueryOptions(context.authClient, context.session)` in `beforeLoad` and redirects to `/login`.

**"My plugin needs the signed-in user's NEAR account."**
In the plugin's `initialize` composed with auth, call `const auth = await plugins.auth({ context })` then `auth.getAuthContext()`; or in a route use the `requireAuth` middleware from `createAuthMiddleware(builder)` and read `context.userId` / `context.near.primaryAccountId` from the narrowed context.

**"`authClient.near.client` no longer compiles."**
Removed in 1.8.1 — switch to `authClient.near.getNearClient()` (throws on the server, so call it only in client-side handlers).

## What comes back when it refuses

| What you see | Where it comes from | Action |
| --- | --- | --- |
| `Unauthorized: Invalid signature` (401) | Server `siwn()` recipient differs from the UI `siwnClient({ recipient })` | Stop — align `variables.siwn.recipient(s)` with the server config; retrying never helps |
| `getSession()` returns null on every SSR render | `createAuthClient({ runtimeConfig })` called without `headers: request.headers` | Stop — pass headers (and `cspNonce`) from `renderOptions` |
| Session cache stale after passkey/social sign-in | Query cached with `staleTime: 60s`; only NEAR flows auto-notify (1.8.2+) | Refresh once: `setQueryData(["session"], fresh)` then `invalidateQueries({ queryKey: ["session"] })` |
| `Sub-account creation unavailable on <network>: parent key not configured...` (503) | `variables.siwn.subAccount` set but no `secrets.parentKey` server-side | Tell the user — the parent key is a server secret, not a config-file field |
| `This NEAR account is already linked to another user` (400) | NEAR account bound to a different user row | Stop — tell the user; do not retry |
| `Cannot unlink last authentication method. Link another account first.` (400) | Last auth method on the user | Tell the user to link another account first |
76 changes: 31 additions & 45 deletions packages/better-near-auth/skills/client/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ sources:
- "elliotBraem/better-near-auth:src/types.ts"
- "elliotBraem/better-near-auth:README.md"
- "elliotBraem/better-near-auth:LLM.txt"
- "elliotBraem/better-near-auth:src/index.ts"
---

# Better-Near-Auth — Client Integration
Expand Down Expand Up @@ -229,51 +230,7 @@ const result = await authClient.near.view({

## Client Actions Reference

### authClient.near

| Method | Returns | Description |
| ------ | ------- | ----------- |
| `nonce(params)` | `Promise<Response<NonceResponse>>` | Request nonce from server |
| `verify(params)` | `Promise<Response<VerifyResponse>>` | Verify NEP-413 signature |
| `getProfile(accountId?)` | `Promise<Response<Profile>>` | Get NEAR profile |
| `view(params)` | `Promise<Response<ViewResponse>>` | Server-side contract view call |
| `getAccountId()` | `string \| null` | The user's NEAR account ID. Prefers the live NearConnect connection; falls back to the primary SIWN-linked account on the session (`session.user.nearAccount`). Persists across disconnects. |
| `getState()` | `{ accountId, publicKey, networkId } \| null` | Wallet state |
| `isWalletConnected()` | `boolean` | Whether wallet is actively connected |
| `detectNearAccount()` | `Promise<{ accountId, publicKey, networkId } \| null>` | Silently probe for a previously authorized wallet without prompting |
| `ensureConnected()` | `Promise<boolean>` | Reconnect wallet if disconnected |
| `disconnect()` | `Promise<void>` | Disconnect wallet |
| `link(callbacks?)` | `Promise<void>` | Link NEAR account to session |
| `unlink(params)` | `Promise<Response>` | Unlink NEAR account |
| `listAccounts()` | `Promise<Response>` | List linked NEAR accounts |
| `setPrimaryAccount(params)` | `Promise<Response<SetPrimaryAccountResponse>>` | Set primary linked NEAR account |
| `createSubAccount(params)` | `Promise<Response<CreateSubAccountResponse>>` | Create a sub-account |
| `checkSubAccountAvailability(params)` | `Promise<Response<CheckSubAccountAvailabilityResponse>>` | Check if a sub-account name is available |
| `buildSignedDelegateAction(receiverId, buildActions)` | `Promise<string>` | Build + sign delegate action, returns base64 payload |
| `relayTransaction({ payload })` | `Promise<Response<RelayResponse>>` | Submit delegate action to relayer |
| `getRelayStatus(txHash)` | `Promise<Response<RelayStatusResponse>>` | Check relayed tx status |
| `getRelayerInfo()` | `Promise<Response<RelayerInfo>>` | Get relayer info and balance |
| `relayHistory()` | `Promise<Response<RelayHistoryResponse>>` | List relayed transactions |
| `setNetwork(network)` | `void` | Switch active network (mainnet/testnet) |
| `getNetwork()` | `"mainnet" \| "testnet"` | Get currently active network |
| `getSupportedNetworks()` | `("mainnet" \| "testnet")[]` | List supported networks |
| `getRecipient(network?)` | `string` | Get configured recipient for a network |
| `getNearClient()` | `Near` | Access near-kit Near instance (throws on server). Returns the `Near` client for direct transactions. |

### authClient.signIn

| Method | Description |
| ------ | ----------- |
| `near(callbacks?)` | Connect wallet, sign message, verify — single popup |

### Callback Interface

```typescript
interface AuthCallbacks {
onSuccess?: () => void;
onError?: (error: Error & { status?: number; code?: string }) => void;
}
```
See [client-actions](references/client-actions.md) for the full `authClient.near.*` method table, `authClient.signIn.near`, and the `AuthCallbacks` interface (`onSuccess` / `onError`).

## Common Mistakes

Expand Down Expand Up @@ -493,3 +450,32 @@ await authClient.near.createSubAccount({
Always check availability first to avoid unnecessary server round-trips and 409 CONFLICT errors. The availability check is cheap (regex + length check client-side, then RPC account lookup server-side).

Source: src/client.ts:537-548, src/index.ts:1406-1412

## The tasks you will actually be given

**"Add NEAR wallet sign-in and show the connected account in the header."**
Create one `authClient` with `siwnClient({ recipient })` (in this repo that factory lives in `packages/everything-dev/src/ui/auth.ts`, re-exported by `ui/src/lib/auth.ts`), call `authClient.signIn.near({ onSuccess, onError })`, and render `useNearAccountId(authClient)` from `better-near-auth/react` — never `getAccountId()` during render (not reactive).

**"Let users write on-chain without paying gas."**
`const payload = await authClient.near.buildSignedDelegateAction(receiverId, builder)` → `await authClient.near.relayTransaction({ payload })` → poll `authClient.near.getRelayStatus(txHash)` until `status` is `"completed"` or `"failed"`.

**"Create a per-user sub-account after sign-in."**
`checkSubAccountAvailability({ subAccountName })` first — the client returns `{ available: false, reason: "invalid" }` locally for bad names — then `createSubAccount({ subAccountName, publicKey })` with the user's key.

**"The account ID disappears when the user disconnects the wallet."**
Read `useNearAccountId(authClient)` instead of `getState().accountId` — the getter falls back to the primary SIWN-linked session account (`session.user.nearAccount`) when NearConnect is disconnected or uninitialized. Use `isWalletConnected()` only to decide whether signing is possible.

## What comes back when it refuses

| What you see | Where it comes from | Action |
| --- | --- | --- |
| `Wallet not initialized for <net> — this operation requires a browser environment` | Signing op called during SSR (`ensureConnected`, `buildSignedDelegateAction`, `signWithWallet`) | Stop — gate the call behind a client-only component/effect |
| `Wallet sign-in was cancelled or failed` | User closed the wallet popup | Tell the user; retry on demand, not automatically |
| `NEAR network changed while signing in` / `while connecting wallet` | Active network switched mid-flow | Retry once once the network is stable |
| `No NEAR account found — please sign in with your NEAR wallet` | Signing op with no wallet or session account | Tell the user to sign in |
| `Wallet connection required — please approve the connection to sign` | Wallet disconnected; `ensureConnected` prompt declined | Tell the user to approve the reconnect prompt |
| `Unauthorized: Invalid signature` (401, from verify) | Client recipient ≠ server `siwn()` recipient | Stop — fix `siwnClient({ recipient })`; the signature is valid, just for the wrong recipient |
| `Unauthorized: Nonce already used (replay attack detected)` (401) | Nonce replayed | Retry once with a fresh nonce from the `/near/nonce` endpoint |
| `No NEAR account linked to session` (401, relay/sub-account) | No SIWN-linked primary account | Tell the user to sign in |
| Availability `reason`: `taken`, `too-long`, `not-configured` | Server lookup in src/index.ts | Stop — pick another name (`not-configured` means fix server config) |
| 409 `Account <id> already exists on <network>` | `createSubAccount` without availability check | Stop — check availability first |
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Client Actions Reference — authClient.near / authClient.signIn

Full method tables for the `siwnClient()` Better Auth client plugin. See the `client` SKILL.md for setup, patterns, and common mistakes.

## authClient.near

| Method | Returns | Description |
| ------ | ------- | ----------- |
| `nonce(params)` | `Promise<Response<NonceResponse>>` | Request nonce from server |
| `verify(params)` | `Promise<Response<VerifyResponse>>` | Verify NEP-413 signature |
| `getProfile(accountId?)` | `Promise<Response<Profile>>` | Get NEAR profile |
| `view(params)` | `Promise<Response<ViewResponse>>` | Server-side contract view call |
| `getAccountId()` | `string \| null` | The user's NEAR account ID. Prefers the live NearConnect connection; falls back to the primary SIWN-linked account on the session (`session.user.nearAccount`). Persists across disconnects. |
| `getState()` | `{ accountId, publicKey, networkId } \| null` | Wallet state |
| `isWalletConnected()` | `boolean` | Whether wallet is actively connected |
| `detectNearAccount()` | `Promise<{ accountId, publicKey, networkId } \| null>` | Silently probe for a previously authorized wallet without prompting |
| `ensureConnected()` | `Promise<boolean>` | Reconnect wallet if disconnected |
| `disconnect()` | `Promise<void>` | Disconnect wallet |
| `link(callbacks?)` | `Promise<void>` | Link NEAR account to session |
| `unlink(params)` | `Promise<Response>` | Unlink NEAR account |
| `listAccounts()` | `Promise<Response>` | List linked NEAR accounts |
| `setPrimaryAccount(params)` | `Promise<Response<SetPrimaryAccountResponse>>` | Set primary linked NEAR account |
| `createSubAccount(params)` | `Promise<Response<CreateSubAccountResponse>>` | Create a sub-account |
| `checkSubAccountAvailability(params)` | `Promise<Response<CheckSubAccountAvailabilityResponse>>` | Check if a sub-account name is available |
| `buildSignedDelegateAction(receiverId, buildActions)` | `Promise<string>` | Build + sign delegate action, returns base64 payload |
| `relayTransaction({ payload })` | `Promise<Response<RelayResponse>>` | Submit delegate action to relayer |
| `getRelayStatus(txHash)` | `Promise<Response<RelayStatusResponse>>` | Check relayed tx status |
| `getRelayerInfo()` | `Promise<Response<RelayerInfo>>` | Get relayer info and balance |
| `relayHistory()` | `Promise<Response<RelayHistoryResponse>>` | List relayed transactions |
| `setNetwork(network)` | `void` | Switch active network (mainnet/testnet) |
| `getNetwork()` | `"mainnet" \| "testnet"` | Get currently active network |
| `getSupportedNetworks()` | `("mainnet" \| "testnet")[]` | List supported networks |
| `getRecipient(network?)` | `string` | Get configured recipient for a network |
| `getNearClient()` | `Near` | Access near-kit Near instance (throws on server). Returns the `Near` client for direct transactions. |

## authClient.signIn

| Method | Description |
| ------ | ----------- |
| `near(callbacks?)` | Connect wallet, sign message, verify — single popup |

## Callback Interface

```typescript
interface AuthCallbacks {
onSuccess?: () => void;
onError?: (error: Error & { status?: number; code?: string }) => void;
}
```
Loading
Loading