@com55/node-red-ai-access 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +388 -0
- package/LICENSE +22 -0
- package/README.md +333 -2
- package/ai-access.cjs +8 -0
- package/ai-access.html +34 -0
- package/dist/plugin.cjs +5487 -0
- package/dist/plugin.d.cts +707 -0
- package/docs/README.md +28 -0
- package/docs/api/openapi.yaml +2589 -0
- package/docs/errors.md +83 -0
- package/docs/mcp-clients.md +78 -0
- package/docs/mcp-tools.md +148 -0
- package/docs/releasing.md +82 -0
- package/package.json +135 -4
package/README.md
CHANGED
|
@@ -1,3 +1,334 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Node-RED AI Access
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://opensource.org/licenses/MIT)
|
|
4
|
+
[](https://nodejs.org/)
|
|
5
|
+
[](https://www.typescriptlang.org/)
|
|
6
|
+
[](https://github.com/com55/node-red-ai-access/actions)
|
|
7
|
+
|
|
8
|
+
Temporary, owner-approved Node-RED access for AI clients. Ships as a **Node-RED
|
|
9
|
+
4.x+ plugin** with:
|
|
10
|
+
|
|
11
|
+
- an admin page at `/mcp`
|
|
12
|
+
- a versioned REST API under `/mcp/api/v1`
|
|
13
|
+
- an MCP JSON-RPC endpoint at `/mcp/rpc` (Streamable HTTP) over the same
|
|
14
|
+
services
|
|
15
|
+
|
|
16
|
+
**Git mental model:** a proposal is like a pull request. Human approve = merge;
|
|
17
|
+
reject = close; withdraw = the author closes their own PR. If `rev` /
|
|
18
|
+
`expectedRev` does not match, treat it as a conflict: re-read the flow, rebase
|
|
19
|
+
the edit, and resubmit. Every access link can see all proposals
|
|
20
|
+
(`mine: true|false`).
|
|
21
|
+
|
|
22
|
+
Derived from
|
|
23
|
+
[ziv-daniel/node-red-mcp](https://github.com/ziv-daniel/node-red-mcp).
|
|
24
|
+
|
|
25
|
+
## Install (Node-RED plugin)
|
|
26
|
+
|
|
27
|
+
Requirements: **Node-RED 4.x or newer**, Node.js 20+, and **`adminAuth`
|
|
28
|
+
enabled** (recommended). Without `adminAuth`, AI features stay off unless you
|
|
29
|
+
opt in (see Configuration).
|
|
30
|
+
|
|
31
|
+
1. Install into your Node-RED user directory (or via the palette):
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
cd ~/.node-red
|
|
35
|
+
npm install @com55/node-red-ai-access
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
2. Restart Node-RED.
|
|
39
|
+
3. Log in to the editor, then open **`/mcp`** on the admin root (for example
|
|
40
|
+
`http://localhost:1880/mcp`, or `{httpAdminRoot}/mcp` when `httpAdminRoot` is
|
|
41
|
+
set).
|
|
42
|
+
|
|
43
|
+
The `/mcp` HTML shell is served without adminAuth so the page can read the
|
|
44
|
+
editor token from browser `localStorage` (Node-RED does not use cookies) and
|
|
45
|
+
call APIs with `Authorization: Bearer`. If no valid token is present, the page
|
|
46
|
+
redirects to the editor login. All data and actions stay behind permission
|
|
47
|
+
checks.
|
|
48
|
+
|
|
49
|
+
On `/mcp` you can create access links, review proposals (with a readable
|
|
50
|
+
node/property diff against the live flow), approve or reject changes, and read a
|
|
51
|
+
human-readable audit log. Users with `read` (including guest, if configured) get
|
|
52
|
+
a read-only view; only users with `*` can create/revoke links and approve/reject
|
|
53
|
+
proposals.
|
|
54
|
+
|
|
55
|
+
## Configuration
|
|
56
|
+
|
|
57
|
+
In `settings.js`:
|
|
58
|
+
|
|
59
|
+
```js
|
|
60
|
+
module.exports = {
|
|
61
|
+
// adminAuth: { ... }, // required for AI features in normal setups
|
|
62
|
+
aiAccess: {
|
|
63
|
+
// Master switch (default: true). Also editable in the editor sidebar tab.
|
|
64
|
+
enabled: true,
|
|
65
|
+
// Local-only opt-in when adminAuth is disabled (default: false)
|
|
66
|
+
allowWithoutAdminAuth: false,
|
|
67
|
+
// Access-link default TTL in seconds (default: 14400 = 4h)
|
|
68
|
+
defaultLinkTtlSeconds: 14400,
|
|
69
|
+
// Max concurrent active access links (default: 10)
|
|
70
|
+
maxActiveLinks: 10,
|
|
71
|
+
// Deploy type used on approve (default: "nodes"; also "flows" | "full")
|
|
72
|
+
defaultDeploymentType: 'nodes',
|
|
73
|
+
// In-memory rate limits for /mcp/api and /mcp/a (per IP + per access token)
|
|
74
|
+
rateLimitWindowMs: 60000,
|
|
75
|
+
rateLimitMaxPerIp: 600,
|
|
76
|
+
rateLimitMaxPerToken: 300,
|
|
77
|
+
// Public base URL for absolute links (access-link url, reviewUrl,
|
|
78
|
+
// accessLinksUrl). Set this behind a TLS-terminating proxy (default: unset)
|
|
79
|
+
// publicBaseUrl: 'https://nodered.example.com',
|
|
80
|
+
// Browser origins allowed to call /mcp/a, /mcp/api, and /mcp/rpc
|
|
81
|
+
// cross-origin (default: none — no CORS headers are sent)
|
|
82
|
+
// corsAllowedOrigins: ['https://app.example.com'],
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Absolute URLs behind a reverse proxy
|
|
88
|
+
|
|
89
|
+
Access-link `url`, proposal `reviewUrl`, and `accessLinksUrl` (in
|
|
90
|
+
`token_expired`) are absolute URLs built by one shared helper:
|
|
91
|
+
|
|
92
|
+
1. `aiAccess.publicBaseUrl`, when set, is used as-is (scheme, host, and an
|
|
93
|
+
optional path prefix). It is read from `settings.js` only.
|
|
94
|
+
2. Otherwise, when Express `trust proxy` is enabled (for example
|
|
95
|
+
`httpServerOptions: { 'trust proxy': true }`), the first `X-Forwarded-Proto`
|
|
96
|
+
and `X-Forwarded-Host` values are used.
|
|
97
|
+
3. Otherwise the request `Host` header and the connection protocol are used;
|
|
98
|
+
`X-Forwarded-*` headers are ignored so clients cannot spoof link origins.
|
|
99
|
+
|
|
100
|
+
Forwarded and `Host` values are validated: the scheme must be `http` or `https`
|
|
101
|
+
(else the connection scheme is used) and the host must be a DNS name, IPv4, or
|
|
102
|
+
bracketed IPv6 literal with an optional port. An invalid forwarded host falls
|
|
103
|
+
back to `Host`; an invalid `Host` yields relative paths. The MCP client snippets
|
|
104
|
+
in the GET-link guide use the same origin, and show a `https://YOUR-NODE-RED`
|
|
105
|
+
placeholder only when no origin can be determined.
|
|
106
|
+
|
|
107
|
+
Behind a TLS-terminating proxy (for example Cloudflare) with neither option set,
|
|
108
|
+
links therefore start with `http://`.
|
|
109
|
+
|
|
110
|
+
### CORS
|
|
111
|
+
|
|
112
|
+
CORS only affects code running in a browser (a web page calling the plugin from
|
|
113
|
+
another origin). Server-side AI fetchers and agents (for example Gemini,
|
|
114
|
+
ChatGPT, or an MCP client running outside a browser) ignore CORS and are not
|
|
115
|
+
affected by this setting.
|
|
116
|
+
|
|
117
|
+
By default the AI-facing endpoints (`/mcp/a/*`, `/mcp/api/*`, `/mcp/rpc`) send
|
|
118
|
+
**no** CORS headers, overriding Node-RED's built-in
|
|
119
|
+
`Access-Control-Allow-Origin: *` for those paths. To allow a browser app, list
|
|
120
|
+
its origin in `aiAccess.corsAllowedOrigins` (`settings.js` only, restart
|
|
121
|
+
required). For a listed `Origin` the response echoes it in
|
|
122
|
+
`Access-Control-Allow-Origin` with `Vary: Origin`, and `OPTIONS` preflight
|
|
123
|
+
returns `204` with allowed methods (`GET, POST, DELETE, OPTIONS`) and headers
|
|
124
|
+
(`Authorization`, `Content-Type`, `Accept`, `Mcp-Session-Id`,
|
|
125
|
+
`Mcp-Protocol-Version`, `Last-Event-ID`). Origins match case-insensitively and
|
|
126
|
+
the configured origin is echoed; malformed origins are rejected. Exposed headers
|
|
127
|
+
include `Mcp-Session-Id`, `WWW-Authenticate`, `Retry-After`, and the
|
|
128
|
+
`RateLimit*` headers. Other origins get no CORS headers.
|
|
129
|
+
|
|
130
|
+
### Settings precedence
|
|
131
|
+
|
|
132
|
+
Effective values are resolved **without restart** in this order (highest wins):
|
|
133
|
+
|
|
134
|
+
1. **Disk** — `<userDir>/.ai-access/settings.json` (editor sidebar **AI Access**
|
|
135
|
+
tab, or `PUT /mcp/api/v1/settings`)
|
|
136
|
+
2. **`settings.js`** — `aiAccess` block (install / bootstrap defaults)
|
|
137
|
+
3. **Built-in defaults** — as documented above
|
|
138
|
+
|
|
139
|
+
Only keys present on disk override; omitted keys fall through to `settings.js`,
|
|
140
|
+
then defaults. The editor right-hand sidebar tab (**AI Access**, next to
|
|
141
|
+
Information / Help / Debug / Configuration nodes / Context Data) lets `*` admins
|
|
142
|
+
change enable/disable, default deploy type, and default access-link TTL; `read`
|
|
143
|
+
users see the same values read-only. A button opens the `/mcp` page. When
|
|
144
|
+
`adminAuth` is off, the tab exposes the `allowWithoutAdminAuth` opt-in with a
|
|
145
|
+
clear local-only warning.
|
|
146
|
+
|
|
147
|
+
Storage lives under **`<userDir>/.ai-access`** (`settings.json`,
|
|
148
|
+
`proposals/*.json`, and append-only `logs/audit.jsonl`). Directories use mode
|
|
149
|
+
`0700`, files `0600`. Proposal JSON is capped at **1_500_000 bytes**; oversized
|
|
150
|
+
proposal JSON or audit lines return HTTP **413** with `code: payload_too_large`
|
|
151
|
+
(never silently truncated). Malformed JSON returns **400** JSON (REST
|
|
152
|
+
`invalid_json`, MCP JSON-RPC `-32700`) — never HTML stack traces (middleware is
|
|
153
|
+
attached to the editor-api app that owns body-parser, not only the nested plugin
|
|
154
|
+
`httpAdmin`). Unauthenticated `/mcp/api/v1` admin calls return JSON
|
|
155
|
+
`{ code: "auth_required" }` rather than plain-text `Unauthorized`.
|
|
156
|
+
|
|
157
|
+
## Docs
|
|
158
|
+
|
|
159
|
+
- [Channel index](docs/README.md) — GET link / REST / MCP overview
|
|
160
|
+
- [MCP client setup](docs/mcp-clients.md) — Claude Desktop, Cursor, VS Code, CLI
|
|
161
|
+
- [MCP tools reference](docs/mcp-tools.md) — generated from live `tools/list`
|
|
162
|
+
- [Error codes](docs/errors.md) — code, status, hint, channels
|
|
163
|
+
- [OpenAPI 3.1](docs/api/openapi.yaml) — also `GET /mcp/api/v1/openapi.yaml`
|
|
164
|
+
(JSON: `GET /mcp/api/v1/openapi.json`)
|
|
165
|
+
|
|
166
|
+
Regenerate the MCP tools doc after tool changes: `yarn generate:mcp-tools`.
|
|
167
|
+
|
|
168
|
+
## REST API
|
|
169
|
+
|
|
170
|
+
OpenAPI: **`GET /mcp/api/v1/openapi.yaml`** or
|
|
171
|
+
**`GET /mcp/api/v1/openapi.json`** (also in
|
|
172
|
+
[`docs/api/openapi.yaml`](docs/api/openapi.yaml)).
|
|
173
|
+
|
|
174
|
+
| Path | Purpose |
|
|
175
|
+
| ---------------------------------------- | ------------------------------------------------ |
|
|
176
|
+
| `GET /mcp` | Admin UI (status, links, proposals, audit log) |
|
|
177
|
+
| `GET /mcp/a/<token>` | Self-contained GET link (markdown docs) |
|
|
178
|
+
| `GET /mcp/a/<token>/…` | Token-scoped GET status/flows/propose/drafts |
|
|
179
|
+
| `POST /mcp/rpc` | MCP Streamable HTTP (access-link Bearer; tools) |
|
|
180
|
+
| `GET /mcp/api/v1` | Discovery index (no auth; endpoints + auth hint) |
|
|
181
|
+
| `GET /mcp/api/v1/openapi.yaml` | OpenAPI 3.1 specification (YAML) |
|
|
182
|
+
| `GET /mcp/api/v1/openapi.json` | OpenAPI 3.1 specification (JSON) |
|
|
183
|
+
| `GET /mcp/api/v1/status` | Plugin status JSON (admin or access link) |
|
|
184
|
+
| `GET /mcp/api/v1/settings` | Effective AI Access settings (admin `read`) |
|
|
185
|
+
| `PUT /mcp/api/v1/settings` | Update settings (`*`; persists to disk, hot) |
|
|
186
|
+
| `GET /mcp/api/v1/links` | List active access links (`*` only) |
|
|
187
|
+
| `POST /mcp/api/v1/links` | Create access link (`*` only; token once) |
|
|
188
|
+
| `DELETE /mcp/api/v1/links/:id` | Revoke access link (`*` only) |
|
|
189
|
+
| `GET /mcp/api/v1/flows` | List flows + rev (admin `read` or access link) |
|
|
190
|
+
| `GET /mcp/api/v1/flows/:id` | Get one flow + rev (admin `read` or access link) |
|
|
191
|
+
| `POST /mcp/api/v1/proposals` | Create proposal (access-link Bearer only) |
|
|
192
|
+
| `GET /mcp/api/v1/proposals` | List proposals (admin: all; link: own) |
|
|
193
|
+
| `GET /mcp/api/v1/proposals/:id` | Get proposal (admin: any; link: own) |
|
|
194
|
+
| `GET /mcp/api/v1/proposals/:id/diff` | Diff proposal vs current flow (admin `read`/own) |
|
|
195
|
+
| `POST /mcp/api/v1/proposals/:id/approve` | Approve + deploy (`*`; optional deploymentType) |
|
|
196
|
+
| `POST /mcp/api/v1/proposals/:id/reject` | Reject with reason (`*`; no flow mutate) |
|
|
197
|
+
| `GET /mcp/api/v1/logs` | Append-only audit log (admin `read`) |
|
|
198
|
+
|
|
199
|
+
### MCP JSON-RPC (`POST /mcp/rpc`)
|
|
200
|
+
|
|
201
|
+
AI clients can use the official MCP Streamable HTTP transport against
|
|
202
|
+
`{httpAdminRoot}/mcp/rpc` with the same access-link Bearer token as the REST
|
|
203
|
+
API. Tools mirror the AI-facing REST surface only:
|
|
204
|
+
|
|
205
|
+
`get_status`, `list_flows`, `get_flow`, `submit_proposal` (including
|
|
206
|
+
`replace_flow` with `expectedRev`; `expected_rev` accepted as alias; optional
|
|
207
|
+
`title` ≤80 chars), `list_proposals` (alias `list_my_proposals`),
|
|
208
|
+
`get_proposal`. Tool descriptions document purpose, inputs, returns, and when to
|
|
209
|
+
use them. Proposal create/list/get responses include `title`, `displayName`
|
|
210
|
+
(`title ·` **server-local** datetime with explicit offset, e.g.
|
|
211
|
+
`16 Oct 01:35 +00:00`), `reviewUrl` (absolute URL using the request
|
|
212
|
+
Host/protocol — `X-Forwarded-*` only when Express `trust proxy` is set), and
|
|
213
|
+
`reviewPath` (relative `/mcp#proposal=<id>`). `createdAt` remains ISO-8601. The
|
|
214
|
+
`/mcp` page renders times in the browser's local timezone. AI clients should
|
|
215
|
+
refer to proposals by `displayName` + `reviewUrl`, not the raw id. For
|
|
216
|
+
access-link callers, `get_status` reports `permission: null` and
|
|
217
|
+
`caller.kind: "access-link"` (expected — AI tokens are not admin sessions).
|
|
218
|
+
|
|
219
|
+
Approve, reject, and access-link management stay human-only on the `/mcp` admin
|
|
220
|
+
page (and the corresponding REST routes). Rate limits, payload size limits, and
|
|
221
|
+
audit logging match REST. Missing tokens return **401** `access_link_missing`;
|
|
222
|
+
known-expired tokens return **401** `token_expired`; other invalid/revoked
|
|
223
|
+
tokens return **401** `access_link_invalid` (all with `WWW-Authenticate`)
|
|
224
|
+
telling the AI to ask the user for a new access link. Every error JSON includes
|
|
225
|
+
a `code`. `token_expired` also includes `expiredAt` and `accessLinksUrl` (the
|
|
226
|
+
`/mcp` page's Access links section). Access-link `/status` includes `expiresAt`,
|
|
227
|
+
`minutesRemaining` (rounded up), and `secondsRemaining`. See `docs/errors.md`
|
|
228
|
+
(generated from `src/plugin/error-registry.ts`).
|
|
229
|
+
|
|
230
|
+
Access links are **in memory** (default TTL **4h**, max 10). Caps: **get_link ≤
|
|
231
|
+
24h**, **rest/mcp ≤ 30d**. Creating a link returns the raw `token` plus a
|
|
232
|
+
self-contained `url` for `get_link` (`/mcp/a/<token>`). While the link remains
|
|
233
|
+
active, `GET /links` includes `url` again so admins can re-copy from `/mcp` (the
|
|
234
|
+
raw `token` field stays create-only). After a restart every old token is
|
|
235
|
+
invalid.
|
|
236
|
+
|
|
237
|
+
Call `list_flows` or `get_flow` first and pass the returned `rev` as
|
|
238
|
+
`expectedRev`. Submit checks the live rev (**409** if stale). Approve may send
|
|
239
|
+
`deploymentType`: `nodes` (default), `flows`, or `full`. A stale `expectedRev`
|
|
240
|
+
at approve marks the proposal **rejected** with a resubmit message; other deploy
|
|
241
|
+
errors mark it **failed**. Reject never mutates flows.
|
|
242
|
+
|
|
243
|
+
### Flow runtime adapter
|
|
244
|
+
|
|
245
|
+
- **RedAdapter** — in-process flows API (`RED.runtime.flows`, or the initialized
|
|
246
|
+
`@node-red/runtime` singleton when the plugin RED object has no `.runtime`)
|
|
247
|
+
- **HttpAdapter** — localhost Admin API v2 fallback only when credentials are
|
|
248
|
+
available (or adminAuth is off). Never selected silently without credentials
|
|
249
|
+
when adminAuth is on; upstream 401/403 becomes `plugin_misconfigured`
|
|
250
|
+
- **`updateFlow`** — requires `expectedRev`; default deployment type `nodes`
|
|
251
|
+
|
|
252
|
+
## AI quickstart
|
|
253
|
+
|
|
254
|
+
### Self-contained GET link (chat AIs that can only HTTP GET)
|
|
255
|
+
|
|
256
|
+
1. An owner with `*` opens `/mcp` → **Access links** → create a **GET link**
|
|
257
|
+
(default TTL **4 hours**, max **24 hours**). Copy the full URL
|
|
258
|
+
`http(s)://<host><httpAdminRoot>/mcp/a/<token>` (or **Copy link for AI**).
|
|
259
|
+
2. The AI opens that URL for markdown docs, then uses GET-only routes under the
|
|
260
|
+
same path (`/status`, `/flows`, `/flows/<id>`, `/proposals`, `/propose` with
|
|
261
|
+
preferred `replace_text` for small string edits, chunked `/draft/…` for large
|
|
262
|
+
HTML/fields; URL soft-cap ~8KB; prefer ~4–5 KB raw chunks — base64url adds
|
|
263
|
+
~33%). Patch / `mode=json` drafts support
|
|
264
|
+
`nodes:[{op:'add'|'remove'|'set',...}]` (or a full `replace_flow` document).
|
|
265
|
+
Field drafts validate node/field at `draft/new`. Unknown `/mcp/a/*` paths
|
|
266
|
+
return JSON `not_found` without echoing the token.
|
|
267
|
+
|
|
268
|
+
Responses on GET links default to HTML, because some AI web fetchers only
|
|
269
|
+
read `text/html`. The HTML is a minimal document (no scripts or external
|
|
270
|
+
resources) holding the same markdown or pretty-printed JSON, fully escaped in
|
|
271
|
+
one `<pre>`, with relative `./…` links clickable. Request the real type via
|
|
272
|
+
Accept or `?format=`: `Accept: text/markdown` or `?format=md` for the guide,
|
|
273
|
+
`Accept: application/json` or `?format=json` for JSON. An explicit
|
|
274
|
+
`Accept: text/plain` returns the same raw text as `text/plain`. No Accept,
|
|
275
|
+
`*/*`, `text/html`, or browser-style lists get HTML. Status codes and data
|
|
276
|
+
are unchanged; every response carries `Vary: Accept`,
|
|
277
|
+
`Cache-Control: no-store`, `Referrer-Policy: no-referrer`,
|
|
278
|
+
`X-Robots-Tag: noindex, nofollow`, and `X-Content-Type-Options: nosniff`, and
|
|
279
|
+
HTML adds a restrictive `Content-Security-Policy`. REST and MCP endpoints are
|
|
280
|
+
not affected.
|
|
281
|
+
|
|
282
|
+
3. Changes become **pending proposals**; a human approves on `/mcp`. JSON
|
|
283
|
+
responses use `Cache-Control: no-store`.
|
|
284
|
+
|
|
285
|
+
### Bearer REST / MCP
|
|
286
|
+
|
|
287
|
+
1. Optional: `GET /mcp/api/v1` (no auth) lists endpoints, auth scheme, OpenAPI,
|
|
288
|
+
and MCP URLs. Spec: `GET /mcp/api/v1/openapi.yaml`.
|
|
289
|
+
2. Send `Authorization: Bearer <token>` to REST and/or MCP:
|
|
290
|
+
|
|
291
|
+
- REST: `GET /mcp/api/v1/status`, `GET /mcp/api/v1/flows`,
|
|
292
|
+
`GET /mcp/api/v1/flows/{id}?summary=1`, `GET /mcp/api/v1/nodes/{id}`,
|
|
293
|
+
`GET /mcp/api/v1/nodes/{id}/field/{name}`, `GET /mcp/api/v1/search`,
|
|
294
|
+
`POST /mcp/api/v1/proposals`, `GET /mcp/api/v1/proposals`
|
|
295
|
+
- MCP: `POST /mcp/rpc` tools `list_flows`, `get_flow` (optional `summary`),
|
|
296
|
+
`get_node`, `get_node_field`, `search_flows`, `submit_proposal`,
|
|
297
|
+
`list_proposals` / alias `list_my_proposals` (same access-link auth and
|
|
298
|
+
limits)
|
|
299
|
+
|
|
300
|
+
3. The owner reviews the proposal on `/mcp` and approves or rejects it.
|
|
301
|
+
4. If the token is invalid, expired, revoked, or cleared by a restart, the API
|
|
302
|
+
returns **401** telling the AI to **ask the user for a new access link**.
|
|
303
|
+
|
|
304
|
+
Example create body:
|
|
305
|
+
|
|
306
|
+
```json
|
|
307
|
+
{
|
|
308
|
+
"type": "replace_flow",
|
|
309
|
+
"expectedRev": "abc123",
|
|
310
|
+
"title": "Rename inject to Hello",
|
|
311
|
+
"payload": {
|
|
312
|
+
"flowId": "tab-1",
|
|
313
|
+
"flow": {
|
|
314
|
+
"id": "tab-1",
|
|
315
|
+
"type": "tab",
|
|
316
|
+
"label": "Main",
|
|
317
|
+
"nodes": []
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
## Development
|
|
324
|
+
|
|
325
|
+
```bash
|
|
326
|
+
yarn install
|
|
327
|
+
yarn build
|
|
328
|
+
yarn test
|
|
329
|
+
yarn quality
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
## License
|
|
333
|
+
|
|
334
|
+
MIT — see [LICENSE](LICENSE).
|
package/ai-access.cjs
ADDED
package/ai-access.html
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
/**
|
|
3
|
+
* Thin loader for the AI Access editor sidebar.
|
|
4
|
+
* Logic lives at GET /mcp/editor/ai-access-sidebar.js (Cache-Control: no-store)
|
|
5
|
+
* so companion-HTML caching does not freeze sidebar UX across plugin updates.
|
|
6
|
+
* Asset version is derived from package.json at build time.
|
|
7
|
+
*/
|
|
8
|
+
(function () {
|
|
9
|
+
if (typeof RED === 'undefined' || !RED.sidebar || !RED.sidebar.addTab) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
if (window.__com55AiAccessSidebarLoading) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
window.__com55AiAccessSidebarLoading = true;
|
|
16
|
+
|
|
17
|
+
var EDITOR_SIDEBAR_ASSET_VERSION = '0.1.1';
|
|
18
|
+
var root = (RED.settings.httpAdminRoot || '/').replace(/\/$/, '');
|
|
19
|
+
var src =
|
|
20
|
+
(root || '') +
|
|
21
|
+
'/mcp/editor/ai-access-sidebar.js?v=' +
|
|
22
|
+
encodeURIComponent(EDITOR_SIDEBAR_ASSET_VERSION);
|
|
23
|
+
|
|
24
|
+
var s = document.createElement('script');
|
|
25
|
+
s.src = src;
|
|
26
|
+
s.async = false;
|
|
27
|
+
s.onerror = function () {
|
|
28
|
+
if (typeof console !== 'undefined' && console.error) {
|
|
29
|
+
console.error('[ai-access] failed to load sidebar script:', src);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
(document.head || document.documentElement).appendChild(s);
|
|
33
|
+
})();
|
|
34
|
+
</script>
|