From e96d155004d06f21ab31346738b178f674be75e6 Mon Sep 17 00:00:00 2001
From: Jaskirat Singh <280064190+jaskiringg@users.noreply.github.com>
Date: Sun, 9 Aug 2026 20:26:54 +0530
Subject: [PATCH 1/5] Add Confluence in-page AI edit/review on SuperDocs.
Sidecar demo plus Forge manifest so the assigned build can run before a Cloud site is ready.
---
.../jaskiringg/confluence-ai/.env.example | 11 +
.../jaskiringg/confluence-ai/.gitignore | 10 +
extensions/jaskiringg/confluence-ai/README.md | 55 +
.../confluence-ai/fixtures/demo-page.adf.json | 89 +
.../jaskiringg/confluence-ai/manifest.yml | 52 +
.../confluence-ai/package-lock.json | 1554 ++++++++++++++
.../jaskiringg/confluence-ai/package.json | 21 +
.../confluence-ai/sidecar/server.js | 40 +
.../jaskiringg/confluence-ai/src/handlers.js | 270 +++
.../jaskiringg/confluence-ai/src/index.js | 15 +
.../jaskiringg/confluence-ai/src/lib/adf.js | 294 +++
.../confluence-ai/src/lib/adf.test.js | 73 +
.../confluence-ai/src/lib/confluence.js | 209 ++
.../confluence-ai/src/lib/superdocs.js | 158 ++
.../confluence-ai/static/ui/index.html | 12 +
.../confluence-ai/static/ui/package-lock.json | 1857 +++++++++++++++++
.../confluence-ai/static/ui/package.json | 18 +
.../confluence-ai/static/ui/src/App.jsx | 316 +++
.../confluence-ai/static/ui/src/api.js | 5 +
.../static/ui/src/forgeBridgeStub.js | 21 +
.../confluence-ai/static/ui/src/main.jsx | 10 +
.../confluence-ai/static/ui/src/styles.css | 80 +
.../confluence-ai/static/ui/vite.config.js | 25 +
23 files changed, 5195 insertions(+)
create mode 100644 extensions/jaskiringg/confluence-ai/.env.example
create mode 100644 extensions/jaskiringg/confluence-ai/.gitignore
create mode 100644 extensions/jaskiringg/confluence-ai/README.md
create mode 100644 extensions/jaskiringg/confluence-ai/fixtures/demo-page.adf.json
create mode 100644 extensions/jaskiringg/confluence-ai/manifest.yml
create mode 100644 extensions/jaskiringg/confluence-ai/package-lock.json
create mode 100644 extensions/jaskiringg/confluence-ai/package.json
create mode 100644 extensions/jaskiringg/confluence-ai/sidecar/server.js
create mode 100644 extensions/jaskiringg/confluence-ai/src/handlers.js
create mode 100644 extensions/jaskiringg/confluence-ai/src/index.js
create mode 100644 extensions/jaskiringg/confluence-ai/src/lib/adf.js
create mode 100644 extensions/jaskiringg/confluence-ai/src/lib/adf.test.js
create mode 100644 extensions/jaskiringg/confluence-ai/src/lib/confluence.js
create mode 100644 extensions/jaskiringg/confluence-ai/src/lib/superdocs.js
create mode 100644 extensions/jaskiringg/confluence-ai/static/ui/index.html
create mode 100644 extensions/jaskiringg/confluence-ai/static/ui/package-lock.json
create mode 100644 extensions/jaskiringg/confluence-ai/static/ui/package.json
create mode 100644 extensions/jaskiringg/confluence-ai/static/ui/src/App.jsx
create mode 100644 extensions/jaskiringg/confluence-ai/static/ui/src/api.js
create mode 100644 extensions/jaskiringg/confluence-ai/static/ui/src/forgeBridgeStub.js
create mode 100644 extensions/jaskiringg/confluence-ai/static/ui/src/main.jsx
create mode 100644 extensions/jaskiringg/confluence-ai/static/ui/src/styles.css
create mode 100644 extensions/jaskiringg/confluence-ai/static/ui/vite.config.js
diff --git a/extensions/jaskiringg/confluence-ai/.env.example b/extensions/jaskiringg/confluence-ai/.env.example
new file mode 100644
index 0000000..b45d5b5
--- /dev/null
+++ b/extensions/jaskiringg/confluence-ai/.env.example
@@ -0,0 +1,11 @@
+SUPERDOCS_API_KEY=your-key-here
+SUPERDOCS_API_BASE=https://api.superdocs.app
+
+# Optional — real Confluence Cloud (sidecar fallback). Leave unset to use the demo page.
+CONFLUENCE_BASE_URL=https://your-site.atlassian.net
+CONFLUENCE_EMAIL=you@example.com
+CONFLUENCE_API_TOKEN=your-confluence-api-token
+CONFLUENCE_PAGE_ID=
+CONFLUENCE_SPACE_ID=
+
+SIDECAR_PORT=8787
diff --git a/extensions/jaskiringg/confluence-ai/.gitignore b/extensions/jaskiringg/confluence-ai/.gitignore
new file mode 100644
index 0000000..2281a64
--- /dev/null
+++ b/extensions/jaskiringg/confluence-ai/.gitignore
@@ -0,0 +1,10 @@
+node_modules/
+static/ui/node_modules/
+static/ui/build/
+.env
+.env.*
+!.env.example
+.DS_Store
+*.log
+.forge/
+dist/
diff --git a/extensions/jaskiringg/confluence-ai/README.md b/extensions/jaskiringg/confluence-ai/README.md
new file mode 100644
index 0000000..e0f3008
--- /dev/null
+++ b/extensions/jaskiringg/confluence-ai/README.md
@@ -0,0 +1,55 @@
+# Confluence in-page AI edit and review
+
+Built by **Jaskirat Singh** for the SuperDocs task.
+
+Edits the Confluence page you are on instead of exporting it away. Select a section, give an instruction, review SuperDocs diffs inline, send a named approver if you want a second gate, then write **only accepted ranges** back as ADF. Tables, code blocks, and macros are placeholders SuperDocs is not allowed to rewrite. Space normalisation is a page-by-page queue. Publish for people who never open Confluence is a branded PDF via SuperDocs export.
+
+## What SuperDocs is used for
+
+- upload / `document_html`
+- `POST /v1/chat/async` with `approval_mode=ask_every_time`
+- approve / reject each proposed change
+- `POST /v1/documents/export` (PDF)
+
+Proposed-change payloads are parsed twice (JSON string → object). Large edits that pause with `awaiting_kind=continue_prompt` are resumed, not retried as a new job.
+
+## Run (sidecar demo, no Forge required)
+
+```bash
+cd extensions/jaskiringg/confluence-ai
+cp .env.example .env # put SUPERDOCS_API_KEY here
+npm install
+npm --prefix static/ui install
+npm run ui:build
+npm run sidecar
+```
+
+Open http://127.0.0.1:8787 — demo IT policy page with a table + code block.
+
+To hit a real Confluence Cloud site from the sidecar, set `CONFLUENCE_BASE_URL`, `CONFLUENCE_EMAIL`, `CONFLUENCE_API_TOKEN`, and a page id.
+
+## Run (Forge, sideload)
+
+1. Create a free Confluence Cloud site.
+2. `npm i -g @forge/cli` then `forge login`.
+3. In this folder: `forge register` (replaces the placeholder `app.id` in `manifest.yml`).
+4. `forge variables set SUPERDOCS_API_KEY`
+5. `FORGE=1 npm --prefix static/ui run build && forge deploy && forge install`
+
+Page action: **SuperDocs AI edit**. Space page: **SuperDocs space queue**.
+
+Marketplace listing is out of scope; sideload is enough.
+
+## Honest limits
+
+- Native Confluence **Publish** cannot be fully intercepted from a content-action modal. We block **our** write-back until the named approver accepts, and store that state on a content property. That is the gate we can actually keep.
+- Sidecar without Confluence credentials uses a synthetic page so the SuperDocs loop can be demoed.
+- ADF mapping covers headings, paragraphs, lists, quotes. Unknown/opaque nodes stay byte-stable on the original ADF.
+
+## Tests
+
+```bash
+npm test
+```
+
+No live SuperDocs key required for unit tests.
diff --git a/extensions/jaskiringg/confluence-ai/fixtures/demo-page.adf.json b/extensions/jaskiringg/confluence-ai/fixtures/demo-page.adf.json
new file mode 100644
index 0000000..d7d22d9
--- /dev/null
+++ b/extensions/jaskiringg/confluence-ai/fixtures/demo-page.adf.json
@@ -0,0 +1,89 @@
+{
+ "type": "doc",
+ "version": 1,
+ "content": [
+ {
+ "type": "heading",
+ "attrs": { "level": 1 },
+ "content": [{ "type": "text", "text": "IT Change Policy — North Region" }]
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "All production changes require a CAB ticket before cutover. Emergency changes still need a follow-up ticket within 24 hours."
+ }
+ ]
+ },
+ {
+ "type": "heading",
+ "attrs": { "level": 2 },
+ "content": [{ "type": "text", "text": "Freeze windows" }]
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "North region freeze is 02:00-06:00 IST on release nights. No master-data edits during freeze."
+ }
+ ]
+ },
+ {
+ "type": "table",
+ "attrs": { "isNumberColumnEnabled": false, "layout": "default" },
+ "content": [
+ {
+ "type": "tableRow",
+ "content": [
+ {
+ "type": "tableHeader",
+ "attrs": {},
+ "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "System" }] }]
+ },
+ {
+ "type": "tableHeader",
+ "attrs": {},
+ "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Owner" }] }]
+ }
+ ]
+ },
+ {
+ "type": "tableRow",
+ "content": [
+ {
+ "type": "tableCell",
+ "attrs": {},
+ "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Order API" }] }]
+ },
+ {
+ "type": "tableCell",
+ "attrs": {},
+ "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Platform" }] }]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "heading",
+ "attrs": { "level": 2 },
+ "content": [{ "type": "text", "text": "Rollback" }]
+ },
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "If error rate exceeds 2% after cutover, roll back within 15 minutes and page the on-call."
+ }
+ ]
+ },
+ {
+ "type": "codeBlock",
+ "attrs": { "language": "bash" },
+ "content": [{ "type": "text", "text": "kubectl rollout undo deploy/order-api" }]
+ }
+ ]
+}
diff --git a/extensions/jaskiringg/confluence-ai/manifest.yml b/extensions/jaskiringg/confluence-ai/manifest.yml
new file mode 100644
index 0000000..b797e24
--- /dev/null
+++ b/extensions/jaskiringg/confluence-ai/manifest.yml
@@ -0,0 +1,52 @@
+modules:
+ confluence:contentAction:
+ - key: ai-edit-review
+ resource: main
+ resolver:
+ function: resolver
+ title: SuperDocs AI edit
+ viewportSize: large
+ confluence:contentBylineItem:
+ - key: ai-review-status
+ resource: main
+ resolver:
+ function: resolver
+ title: AI review
+ confluence:spacePage:
+ - key: space-normalise
+ resource: main
+ resolver:
+ function: resolver
+ title: SuperDocs space queue
+ route: superdocs-queue
+ function:
+ - key: resolver
+ handler: index.handler
+
+resources:
+ - key: main
+ path: static/ui/build
+
+app:
+ runtime:
+ name: nodejs20.x
+ memoryMB: 256
+ id: ari:cloud:ecosystem::app/00000000-0000-4000-8000-000000000001
+
+permissions:
+ scopes:
+ - read:page:confluence
+ - write:page:confluence
+ - read:space:confluence
+ - read:confluence-user
+ - search:confluence
+ - write:confluence-content
+ - read:confluence-content.summary
+ external:
+ fetch:
+ backend:
+ - address: api.superdocs.app
+ category: analytics
+ inScopeEUD: false
+ client:
+ - address: api.superdocs.app
diff --git a/extensions/jaskiringg/confluence-ai/package-lock.json b/extensions/jaskiringg/confluence-ai/package-lock.json
new file mode 100644
index 0000000..bee32e9
--- /dev/null
+++ b/extensions/jaskiringg/confluence-ai/package-lock.json
@@ -0,0 +1,1554 @@
+{
+ "name": "confluence-ai-review",
+ "version": "0.1.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "confluence-ai-review",
+ "version": "0.1.0",
+ "dependencies": {
+ "@forge/api": "^8.0.3",
+ "@forge/resolver": "^2.0.0",
+ "cors": "^2.8.5",
+ "dotenv": "^16.4.7",
+ "express": "^4.21.2",
+ "node-html-parser": "^7.0.1"
+ }
+ },
+ "node_modules/@forge/api": {
+ "version": "8.0.3",
+ "resolved": "https://registry.npmjs.org/@forge/api/-/api-8.0.3.tgz",
+ "integrity": "sha512-qQ8W3pNXhXc1DHWZxH+pGmbXykKldGmtxfJUbnQjukDh8CAAHpXmLxwxRm6MEftW0wENgrPl8kQfGfXE2md1Bg==",
+ "license": "SEE LICENSE IN LICENSE.txt",
+ "dependencies": {
+ "@forge/auth": "1.0.0",
+ "@forge/egress": "3.0.0",
+ "@forge/i18n": "1.0.0",
+ "@forge/manifest": "^13.3.0",
+ "headers-utils": "^3.0.2"
+ },
+ "peerDependencies": {
+ "typescript": ">=5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@forge/auth": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@forge/auth/-/auth-1.0.0.tgz",
+ "integrity": "sha512-lEwaTIS9lZAMeuf9TfuP/OyhoJz1SCoIt5ZbgbgPtG3SV/KYdI2qSW6xafnONy0iAaKJo5D3VDBEbm7K1d+qog==",
+ "license": "SEE LICENSE IN LICENSE.txt",
+ "dependencies": {
+ "tslib": "^2.6.2"
+ },
+ "peerDependencies": {
+ "typescript": ">=5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@forge/egress": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@forge/egress/-/egress-3.0.0.tgz",
+ "integrity": "sha512-uQfoTAb3tk69sfxCwTQTDc08Et+zBQ3AHoY+uXZ8IiZkTsEZrRKk1IoWlOzwiEJHpvFgewTv1quvqI1P/PIb2w==",
+ "license": "SEE LICENSE IN LICENSE.txt",
+ "peerDependencies": {
+ "typescript": ">=5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@forge/i18n": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@forge/i18n/-/i18n-1.0.0.tgz",
+ "integrity": "sha512-zXQX2/vt3MM6EVv0EMiKM5GemHnFXMiNoclua0TzK9vCcQ+iA70Eg06n90ySKsyf3eslaLQtZvi/hfpntjH2Gg==",
+ "license": "SEE LICENSE IN LICENSE.txt",
+ "dependencies": {
+ "lodash": "^4.17.21"
+ },
+ "peerDependencies": {
+ "typescript": ">=5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@forge/manifest": {
+ "version": "13.3.0",
+ "resolved": "https://registry.npmjs.org/@forge/manifest/-/manifest-13.3.0.tgz",
+ "integrity": "sha512-EdXhHz1PMjITk30zxapgHWuoZVrvNxYTjcK37V4HaLV3bo58z/VoVzE9ttkzGf9H7j1bXzPWPXnEioXobr61mA==",
+ "license": "SEE LICENSE IN LICENSE.txt",
+ "dependencies": {
+ "@forge/i18n": "1.0.0",
+ "@sentry/node": "7.106.0",
+ "ajv": "^8.18.0",
+ "ajv-formats": "2.1.1",
+ "cheerio": "^1.2.0",
+ "glob": "^13.0.0",
+ "lodash": "^4.17.21",
+ "mime-types": "^2.1.35",
+ "yaml": "^2.3.4"
+ },
+ "peerDependencies": {
+ "typescript": ">=5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@forge/resolver": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@forge/resolver/-/resolver-2.0.0.tgz",
+ "integrity": "sha512-6TsntUodtRxefZjDTLVT5yvFo4OFEQwLRcN5cniWre0hD2tJ5B2OWw+8r4u1dTjflA0/+rciScWkuoKTRQK+WQ==",
+ "license": "SEE LICENSE IN LICENSE.txt",
+ "peerDependencies": {
+ "typescript": ">=5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@sentry-internal/tracing": {
+ "version": "7.106.0",
+ "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.106.0.tgz",
+ "integrity": "sha512-O8Es6Sa/tP80nfl+8soNfWzeRNFcT484SvjLR8BS3pHM9KDAlwNXyoQhFr2BKNYL1irbq6UF6eku4xCnUKVmqA==",
+ "license": "MIT",
+ "dependencies": {
+ "@sentry/core": "7.106.0",
+ "@sentry/types": "7.106.0",
+ "@sentry/utils": "7.106.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@sentry/core": {
+ "version": "7.106.0",
+ "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.106.0.tgz",
+ "integrity": "sha512-Dc13XtnyFaXup2E4vCbzuG0QKAVjrJBk4qfGwvSJaTuopEaEWBs2MpK6hRzFhsz9S3T0La7c1F/62NptvTUWsQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@sentry/types": "7.106.0",
+ "@sentry/utils": "7.106.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@sentry/node": {
+ "version": "7.106.0",
+ "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.106.0.tgz",
+ "integrity": "sha512-4DIqbu5K7//lK/k2nV8lqKeGQzhu2T1OpJFmiUrjN6fUKWivGFjZrcmQDS7tvhAAyJezkL3LlrNU4tjPHUElPA==",
+ "license": "MIT",
+ "dependencies": {
+ "@sentry-internal/tracing": "7.106.0",
+ "@sentry/core": "7.106.0",
+ "@sentry/types": "7.106.0",
+ "@sentry/utils": "7.106.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@sentry/types": {
+ "version": "7.106.0",
+ "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.106.0.tgz",
+ "integrity": "sha512-oKTkDaL6P9xJC5/zHLRemHTWboUqRYjkJNaZCN63j4kJqGy56wee4vDtDese/NWWn4U4C1QV1h+Mifm2HmDcQg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@sentry/utils": {
+ "version": "7.106.0",
+ "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.106.0.tgz",
+ "integrity": "sha512-bVsePsXLpFu/1sH4rpJrPcnVxW2fXXfGfGxKs6Bm+dkOMbuVTlk/KAzIbdjCDIpVlrMDJmMNEv5xgTFjgWDkjw==",
+ "license": "MIT",
+ "dependencies": {
+ "@sentry/types": "7.106.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/accepts": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "~2.1.34",
+ "negotiator": "0.6.3"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "8.20.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
+ "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3",
+ "fast-uri": "^3.0.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ajv-formats": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
+ "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "ajv": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/array-flatten": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
+ "license": "MIT"
+ },
+ "node_modules/balanced-match": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
+ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
+ "license": "MIT",
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/body-parser": {
+ "version": "1.20.6",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.6.tgz",
+ "integrity": "sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "~3.1.2",
+ "content-type": "~1.0.5",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "~1.2.0",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.4.24",
+ "on-finished": "~2.4.1",
+ "qs": "~6.15.1",
+ "raw-body": "~2.5.3",
+ "type-is": "~1.6.18",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/body-parser/node_modules/iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/boolbase": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
+ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
+ "license": "ISC"
+ },
+ "node_modules/brace-expansion": {
+ "version": "5.0.9",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz",
+ "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==",
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/call-bound": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/cheerio": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.2.0.tgz",
+ "integrity": "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==",
+ "license": "MIT",
+ "dependencies": {
+ "cheerio-select": "^2.1.0",
+ "dom-serializer": "^2.0.0",
+ "domhandler": "^5.0.3",
+ "domutils": "^3.2.2",
+ "encoding-sniffer": "^0.2.1",
+ "htmlparser2": "^10.1.0",
+ "parse5": "^7.3.0",
+ "parse5-htmlparser2-tree-adapter": "^7.1.0",
+ "parse5-parser-stream": "^7.1.2",
+ "undici": "^7.19.0",
+ "whatwg-mimetype": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=20.18.1"
+ },
+ "funding": {
+ "url": "https://github.com/cheeriojs/cheerio?sponsor=1"
+ }
+ },
+ "node_modules/cheerio-select": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz",
+ "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "boolbase": "^1.0.0",
+ "css-select": "^5.1.0",
+ "css-what": "^6.1.0",
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.3",
+ "domutils": "^3.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/fb55"
+ }
+ },
+ "node_modules/content-disposition": {
+ "version": "0.5.4",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
+ "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "5.2.1"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
+ "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
+ "license": "MIT"
+ },
+ "node_modules/cors": {
+ "version": "2.8.6",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
+ "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
+ "license": "MIT",
+ "dependencies": {
+ "object-assign": "^4",
+ "vary": "^1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/css-select": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz",
+ "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "boolbase": "^1.0.0",
+ "css-what": "^6.1.0",
+ "domhandler": "^5.0.2",
+ "domutils": "^3.0.1",
+ "nth-check": "^2.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/fb55"
+ }
+ },
+ "node_modules/css-what": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz",
+ "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">= 6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/fb55"
+ }
+ },
+ "node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/destroy": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/dom-serializer": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
+ "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
+ "license": "MIT",
+ "dependencies": {
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.2",
+ "entities": "^4.2.0"
+ },
+ "funding": {
+ "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
+ }
+ },
+ "node_modules/domelementtype": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
+ "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fb55"
+ }
+ ],
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/domhandler": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
+ "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "domelementtype": "^2.3.0"
+ },
+ "engines": {
+ "node": ">= 4"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/domhandler?sponsor=1"
+ }
+ },
+ "node_modules/domutils": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
+ "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "dom-serializer": "^2.0.0",
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/domutils?sponsor=1"
+ }
+ },
+ "node_modules/dotenv": {
+ "version": "16.6.1",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
+ "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://dotenvx.com"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+ "license": "MIT"
+ },
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/encoding-sniffer": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz",
+ "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==",
+ "license": "MIT",
+ "dependencies": {
+ "iconv-lite": "^0.6.3",
+ "whatwg-encoding": "^3.1.1"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/encoding-sniffer?sponsor=1"
+ }
+ },
+ "node_modules/entities": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
+ "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "license": "MIT"
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/express": {
+ "version": "4.22.2",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz",
+ "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "~1.3.8",
+ "array-flatten": "1.1.1",
+ "body-parser": "~1.20.5",
+ "content-disposition": "~0.5.4",
+ "content-type": "~1.0.4",
+ "cookie": "~0.7.1",
+ "cookie-signature": "~1.0.6",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "finalhandler": "~1.3.1",
+ "fresh": "~0.5.2",
+ "http-errors": "~2.0.0",
+ "merge-descriptors": "1.0.3",
+ "methods": "~1.1.2",
+ "on-finished": "~2.4.1",
+ "parseurl": "~1.3.3",
+ "path-to-regexp": "~0.1.12",
+ "proxy-addr": "~2.0.7",
+ "qs": "~6.15.1",
+ "range-parser": "~1.2.1",
+ "safe-buffer": "5.2.1",
+ "send": "~0.19.0",
+ "serve-static": "~1.16.2",
+ "setprototypeof": "1.2.0",
+ "statuses": "~2.0.1",
+ "type-is": "~1.6.18",
+ "utils-merge": "1.0.1",
+ "vary": "~1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "license": "MIT"
+ },
+ "node_modules/fast-uri": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz",
+ "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/finalhandler": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
+ "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "on-finished": "~2.4.1",
+ "parseurl": "~1.3.3",
+ "statuses": "~2.0.2",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/glob": {
+ "version": "13.0.6",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz",
+ "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==",
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "minimatch": "^10.2.2",
+ "minipass": "^7.1.3",
+ "path-scurry": "^2.0.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
+ "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/he": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+ "license": "MIT",
+ "bin": {
+ "he": "bin/he"
+ }
+ },
+ "node_modules/headers-utils": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/headers-utils/-/headers-utils-3.0.2.tgz",
+ "integrity": "sha512-xAxZkM1dRyGV2Ou5bzMxBPNLoRCjcX+ya7KSWybQD2KwLphxsapUVK6x/02o7f4VU6GPSXch9vNY2+gkU8tYWQ==",
+ "license": "MIT"
+ },
+ "node_modules/htmlparser2": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz",
+ "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==",
+ "funding": [
+ "https://github.com/fb55/htmlparser2?sponsor=1",
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fb55"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.3",
+ "domutils": "^3.2.2",
+ "entities": "^7.0.1"
+ }
+ },
+ "node_modules/htmlparser2/node_modules/entities": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
+ "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "~2.0.0",
+ "inherits": "~2.0.4",
+ "setprototypeof": "~1.2.0",
+ "statuses": "~2.0.2",
+ "toidentifier": "~1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "license": "ISC"
+ },
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "license": "MIT"
+ },
+ "node_modules/lodash": {
+ "version": "4.18.1",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
+ "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
+ "license": "MIT"
+ },
+ "node_modules/lru-cache": {
+ "version": "11.5.2",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz",
+ "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==",
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/media-typer": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/merge-descriptors": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
+ "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/methods": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+ "license": "MIT",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "10.2.6",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz",
+ "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==",
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "brace-expansion": "^5.0.8"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/minipass": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
+ "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT"
+ },
+ "node_modules/negotiator": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/node-html-parser": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-7.1.0.tgz",
+ "integrity": "sha512-iJo8b2uYGT40Y8BTyy5ufL6IVbN8rbm/1QK2xffXU/1a/v3AAa0d1YAoqBNYqaS4R/HajkWIpIfdE6KcyFh1AQ==",
+ "license": "MIT",
+ "dependencies": {
+ "css-select": "^5.1.0",
+ "he": "1.2.0"
+ }
+ },
+ "node_modules/nth-check": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
+ "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "boolbase": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/nth-check?sponsor=1"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/parse5": {
+ "version": "7.3.0",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
+ "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
+ "license": "MIT",
+ "dependencies": {
+ "entities": "^6.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
+ }
+ },
+ "node_modules/parse5-htmlparser2-tree-adapter": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz",
+ "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==",
+ "license": "MIT",
+ "dependencies": {
+ "domhandler": "^5.0.3",
+ "parse5": "^7.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
+ }
+ },
+ "node_modules/parse5-parser-stream": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz",
+ "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==",
+ "license": "MIT",
+ "dependencies": {
+ "parse5": "^7.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
+ }
+ },
+ "node_modules/parse5/node_modules/entities": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
+ "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/path-scurry": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz",
+ "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==",
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "lru-cache": "^11.0.0",
+ "minipass": "^7.1.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/path-to-regexp": {
+ "version": "0.1.13",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz",
+ "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==",
+ "license": "MIT"
+ },
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "license": "MIT",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/qs": {
+ "version": "6.15.3",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz",
+ "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "es-define-property": "^1.0.1",
+ "side-channel": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "2.5.3",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
+ "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "~3.1.2",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.4.24",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/raw-body/node_modules/iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "license": "MIT"
+ },
+ "node_modules/send": {
+ "version": "0.19.2",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
+ "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "fresh": "~0.5.2",
+ "http-errors": "~2.0.1",
+ "mime": "1.6.0",
+ "ms": "2.1.3",
+ "on-finished": "~2.4.1",
+ "range-parser": "~1.2.1",
+ "statuses": "~2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/send/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/serve-static": {
+ "version": "1.16.3",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",
+ "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==",
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "parseurl": "~1.3.3",
+ "send": "~0.19.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+ "license": "ISC"
+ },
+ "node_modules/side-channel": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz",
+ "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.4",
+ "side-channel-list": "^1.0.1",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-list": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
+ "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/statuses": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "license": "0BSD"
+ },
+ "node_modules/type-is": {
+ "version": "1.6.18",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+ "license": "MIT",
+ "dependencies": {
+ "media-typer": "0.3.0",
+ "mime-types": "~2.1.24"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/undici": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz",
+ "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=20.18.1"
+ }
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/utils-merge": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/whatwg-encoding": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
+ "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
+ "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation",
+ "license": "MIT",
+ "dependencies": {
+ "iconv-lite": "0.6.3"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/whatwg-mimetype": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
+ "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/yaml": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz",
+ "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==",
+ "license": "ISC",
+ "bin": {
+ "yaml": "bin.mjs"
+ },
+ "engines": {
+ "node": ">= 14.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/eemeli"
+ }
+ }
+ }
+}
diff --git a/extensions/jaskiringg/confluence-ai/package.json b/extensions/jaskiringg/confluence-ai/package.json
new file mode 100644
index 0000000..ecf07b7
--- /dev/null
+++ b/extensions/jaskiringg/confluence-ai/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "confluence-ai-review",
+ "private": true,
+ "version": "0.1.0",
+ "description": "Confluence Cloud in-page AI edit and review, built on SuperDocs.",
+ "scripts": {
+ "sidecar": "node sidecar/server.js",
+ "ui:dev": "npm --prefix static/ui run dev",
+ "ui:build": "npm --prefix static/ui run build",
+ "test": "node --test src/lib/*.test.js",
+ "start": "npm run ui:build && npm run sidecar"
+ },
+ "dependencies": {
+ "@forge/api": "^8.0.3",
+ "@forge/resolver": "^2.0.0",
+ "cors": "^2.8.5",
+ "dotenv": "^16.4.7",
+ "express": "^4.21.2",
+ "node-html-parser": "^7.0.1"
+ }
+}
diff --git a/extensions/jaskiringg/confluence-ai/sidecar/server.js b/extensions/jaskiringg/confluence-ai/sidecar/server.js
new file mode 100644
index 0000000..5162e22
--- /dev/null
+++ b/extensions/jaskiringg/confluence-ai/sidecar/server.js
@@ -0,0 +1,40 @@
+const path = require("path");
+const fs = require("fs");
+const express = require("express");
+const cors = require("cors");
+require("dotenv").config({ path: path.join(__dirname, "../.env") });
+
+const { handlers } = require("../src/handlers");
+
+const app = express();
+app.use(cors());
+app.use(express.json({ limit: "8mb" }));
+
+app.post("/api/:action", async (req, res) => {
+ const fn = handlers[req.params.action];
+ if (!fn) return res.status(404).json({ error: `Unknown action ${req.params.action}` });
+ try {
+ const data = await fn(req.body || {}, {
+ kind: "sidecar",
+ apiKey: process.env.SUPERDOCS_API_KEY,
+ pageId: req.body?.pageId,
+ spaceId: req.body?.spaceId,
+ });
+ res.json(data);
+ } catch (err) {
+ console.error(req.params.action, err);
+ res.status(err.status || 500).json({ error: err.message || String(err) });
+ }
+});
+
+const uiBuild = path.join(__dirname, "../static/ui/build");
+if (fs.existsSync(uiBuild)) {
+ app.use(express.static(uiBuild));
+ app.get("*", (_req, res) => res.sendFile(path.join(uiBuild, "index.html")));
+}
+
+const port = Number(process.env.SIDECAR_PORT || 8787);
+app.listen(port, () => {
+ console.log(`Confluence AI sidecar http://127.0.0.1:${port}`);
+ console.log(process.env.SUPERDOCS_API_KEY ? "SuperDocs key: set" : "SuperDocs key: MISSING (.env)");
+});
diff --git a/extensions/jaskiringg/confluence-ai/src/handlers.js b/extensions/jaskiringg/confluence-ai/src/handlers.js
new file mode 100644
index 0000000..03a1af4
--- /dev/null
+++ b/extensions/jaskiringg/confluence-ai/src/handlers.js
@@ -0,0 +1,270 @@
+const {
+ adfToHtml,
+ listSections,
+ sliceSectionHtml,
+ applyAcceptedChanges,
+ ensureLocalIds,
+} = require("./lib/adf");
+const {
+ startHitlEdit,
+ getJob,
+ approveChanges,
+ continueJob,
+ exportPdf,
+ HOUSE_STRUCTURE_PROMPT,
+ LETTERHEAD,
+} = require("./lib/superdocs");
+const {
+ getPage,
+ updatePage,
+ listSpacePages,
+ searchUsers,
+ getReviewState,
+ setReviewState,
+ DEMO_PAGE_ID,
+ DEMO_SPACE_ID,
+} = require("./lib/confluence");
+
+const sessions = new Map();
+const reviewMemory = new Map();
+
+function envFrom(ctx) {
+ return {
+ SUPERDOCS_API_KEY: ctx.apiKey || process.env.SUPERDOCS_API_KEY,
+ SUPERDOCS_API_BASE: process.env.SUPERDOCS_API_BASE,
+ CONFLUENCE_BASE_URL: process.env.CONFLUENCE_BASE_URL,
+ CONFLUENCE_EMAIL: process.env.CONFLUENCE_EMAIL,
+ CONFLUENCE_API_TOKEN: process.env.CONFLUENCE_API_TOKEN,
+ };
+}
+
+function sessionKey(pageId) {
+ return `conf-${pageId}`;
+}
+
+async function getContext(payload, ctx) {
+ const env = envFrom(ctx);
+ const pageId = payload.pageId || ctx.pageId || DEMO_PAGE_ID;
+ const spaceId = payload.spaceId || ctx.spaceId || DEMO_SPACE_ID;
+ const page = await getPage(env, pageId);
+ const { sections, doc } = listSections(page.adf);
+ const review = await getReviewState(env, pageId, reviewMemory);
+ return {
+ page: {
+ id: page.id,
+ title: page.title,
+ version: page.version?.number || 1,
+ spaceId: page.spaceId || spaceId,
+ demo: !!page.demo,
+ },
+ sections,
+ review,
+ hasSuperDocsKey: !!env.SUPERDOCS_API_KEY,
+ };
+}
+
+async function loadPageHtml(payload, ctx) {
+ const env = envFrom(ctx);
+ const page = await getPage(env, payload.pageId);
+ const { start, end } = payload;
+ if (start != null && end != null) {
+ const sliced = sliceSectionHtml(page.adf, start, end);
+ sessions.set(sessionKey(page.id), { adf: sliced.doc, page, range: { start, end } });
+ return { html: sliced.html, title: page.title, version: page.version?.number || 1 };
+ }
+ const full = adfToHtml(page.adf);
+ sessions.set(sessionKey(page.id), { adf: full.doc, page, range: null });
+ return { html: full.html, title: page.title, version: page.version?.number || 1 };
+}
+
+async function startEdit(payload, ctx) {
+ const env = envFrom(ctx);
+ const page = await getPage(env, payload.pageId);
+ const review = await getReviewState(env, page.id, reviewMemory);
+ if (review.status === "awaiting_approver" && payload.role !== "approver") {
+ throw new Error("This page is waiting on the named approver. Publish is blocked.");
+ }
+
+ let html;
+ let storedAdf;
+ if (payload.start != null && payload.end != null) {
+ const sliced = sliceSectionHtml(page.adf, payload.start, payload.end);
+ html = sliced.html;
+ storedAdf = sliced.doc;
+ } else {
+ const full = adfToHtml(page.adf);
+ html = full.html;
+ storedAdf = full.doc;
+ }
+
+ const sid = `${sessionKey(page.id)}-${Date.now()}`;
+ const instruction = payload.house
+ ? HOUSE_STRUCTURE_PROMPT
+ : payload.message;
+ if (!instruction || !String(instruction).trim()) {
+ throw new Error("Write an instruction, or use house structure.");
+ }
+
+ const job = await startHitlEdit({
+ apiKey: env.SUPERDOCS_API_KEY,
+ sessionId: sid,
+ documentHtml: html,
+ message: String(instruction).trim(),
+ modelTier: payload.modelTier || "core",
+ });
+
+ sessions.set(sid, {
+ adf: ensureLocalIds(storedAdf),
+ page,
+ range: payload.start != null ? { start: payload.start, end: payload.end } : null,
+ jobId: job.job_id,
+ sessionId: sid,
+ html,
+ });
+
+ return { jobId: job.job_id, sessionId: sid, status: job.status };
+}
+
+async function pollJob(payload, ctx) {
+ const env = envFrom(ctx);
+ const job = await getJob({ apiKey: env.SUPERDOCS_API_KEY, jobId: payload.jobId });
+ if (job.status === "awaiting_approval" && job.metadata?.awaiting_kind === "continue_prompt") {
+ return {
+ status: job.status,
+ awaitingKind: "continue_prompt",
+ continuePrompt: job.metadata.continue_prompt,
+ pendingChanges: [],
+ };
+ }
+ return {
+ status: job.status,
+ awaitingKind: job.metadata?.awaiting_kind || (job.pending_changes?.length ? "hitl" : null),
+ pendingChanges: job.pending_changes || [],
+ error: job.error || null,
+ result: job.status === "completed" ? job.result : null,
+ };
+}
+
+async function decide(payload, ctx) {
+ const env = envFrom(ctx);
+ const sess = sessions.get(payload.sessionId);
+ if (!sess) throw new Error("Session expired. Start the edit again.");
+
+ const out = await approveChanges({
+ apiKey: env.SUPERDOCS_API_KEY,
+ sessionId: payload.sessionId,
+ jobId: payload.jobId,
+ decisions: payload.decisions,
+ });
+
+ sess.decisions = payload.decisions;
+ sessions.set(payload.sessionId, sess);
+ return out;
+}
+
+async function resumeLargeEdit(payload, ctx) {
+ const env = envFrom(ctx);
+ return continueJob({
+ apiKey: env.SUPERDOCS_API_KEY,
+ sessionId: payload.sessionId,
+ jobId: payload.jobId,
+ shouldContinue: payload.continue !== false,
+ });
+}
+
+async function writeBack(payload, ctx) {
+ const env = envFrom(ctx);
+ const sess = sessions.get(payload.sessionId);
+ if (!sess) throw new Error("Session expired. Start the edit again.");
+
+ const review = await getReviewState(env, sess.page.id, reviewMemory);
+ if (review.status === "awaiting_approver") {
+ const actor = payload.actorAccountId || payload.role;
+ if (review.approverAccountId && actor !== review.approverAccountId && payload.role !== "approver") {
+ throw new Error("Named approver has not accepted yet. Write-back is blocked.");
+ }
+ }
+
+ const accepted = (payload.decisions || sess.decisions || []).filter((d) => d.approved !== false);
+ const nextAdf = applyAcceptedChanges(sess.adf, accepted.map((d) => ({
+ ...d,
+ operation: d.operation || "edit",
+ chunk_id: d.chunk_id,
+ new_html: d.new_html,
+ insert_after_chunk_id: d.insert_after_chunk_id,
+ approved: true,
+ })));
+
+ const updated = await updatePage(env, {
+ pageId: sess.page.id,
+ title: sess.page.title,
+ adf: nextAdf,
+ versionNumber: sess.page.version?.number || 1,
+ message: "SuperDocs AI edit (single approved batch)",
+ });
+
+ await setReviewState(env, sess.page.id, { status: "approved", approvedAt: new Date().toISOString() }, reviewMemory);
+ sess.adf = nextAdf;
+ sess.page.version = updated.version;
+ return {
+ version: updated.version?.number || (sess.page.version?.number || 1) + 1,
+ demo: !!updated.demo,
+ title: sess.page.title,
+ };
+}
+
+async function sendToApprover(payload, ctx) {
+ const env = envFrom(ctx);
+ const pageId = payload.pageId;
+ const value = {
+ status: "awaiting_approver",
+ approverAccountId: payload.approverAccountId,
+ approverName: payload.approverName || payload.approverAccountId,
+ sessionId: payload.sessionId,
+ jobId: payload.jobId,
+ requestedAt: new Date().toISOString(),
+ };
+ await setReviewState(env, pageId, value, reviewMemory);
+ return value;
+}
+
+async function listPages(payload, ctx) {
+ const env = envFrom(ctx);
+ const spaceId = payload.spaceId || DEMO_SPACE_ID;
+ const pages = await listSpacePages(env, spaceId);
+ return { spaceId, pages };
+}
+
+async function findUsers(payload, ctx) {
+ const env = envFrom(ctx);
+ return { users: await searchUsers(env, payload.query || "") };
+}
+
+async function exportBrandedPdf(payload, ctx) {
+ const env = envFrom(ctx);
+ const sess = payload.sessionId ? sessions.get(payload.sessionId) : null;
+ const page = await getPage(env, payload.pageId || sess?.page?.id || DEMO_PAGE_ID);
+ const { html } = adfToHtml(sess?.adf || page.adf);
+ const wrapped = `${LETTERHEAD}${html}`;
+ return exportPdf({
+ apiKey: env.SUPERDOCS_API_KEY,
+ html: wrapped,
+ filename: (page.title || "confluence-page").replace(/[^\w.-]+/g, "-").slice(0, 80),
+ });
+}
+
+const handlers = {
+ getContext,
+ loadPageHtml,
+ startEdit,
+ pollJob,
+ decide,
+ resumeLargeEdit,
+ writeBack,
+ sendToApprover,
+ listPages,
+ findUsers,
+ exportBrandedPdf,
+};
+
+module.exports = { handlers };
diff --git a/extensions/jaskiringg/confluence-ai/src/index.js b/extensions/jaskiringg/confluence-ai/src/index.js
new file mode 100644
index 0000000..f911ec8
--- /dev/null
+++ b/extensions/jaskiringg/confluence-ai/src/index.js
@@ -0,0 +1,15 @@
+const Resolver = require("@forge/resolver");
+const { handlers } = require("./handlers");
+
+const resolver = new Resolver();
+
+for (const [name, fn] of Object.entries(handlers)) {
+ resolver.define(name, async (req) => {
+ const pageId = req.context?.extension?.content?.id;
+ const spaceId = req.context?.extension?.space?.id;
+ const apiKey = process.env.SUPERDOCS_API_KEY;
+ return fn(req.payload || {}, { pageId, spaceId, apiKey, kind: "forge", context: req.context });
+ });
+}
+
+exports.handler = resolver.getDefinitions();
diff --git a/extensions/jaskiringg/confluence-ai/src/lib/adf.js b/extensions/jaskiringg/confluence-ai/src/lib/adf.js
new file mode 100644
index 0000000..cc1a172
--- /dev/null
+++ b/extensions/jaskiringg/confluence-ai/src/lib/adf.js
@@ -0,0 +1,294 @@
+const { parse: parseHtml } = require("node-html-parser");
+
+const OPAQUE = new Set([
+ "table",
+ "codeBlock",
+ "extension",
+ "bodiedExtension",
+ "inlineExtension",
+ "mediaSingle",
+ "mediaGroup",
+ "embedCard",
+ "decisionList",
+ "taskList",
+ "layoutSection",
+ "expand",
+ "nestedExpand",
+]);
+
+function textOf(node) {
+ if (!node) return "";
+ if (node.type === "text") return node.text || "";
+ return (node.content || []).map(textOf).join("");
+}
+
+function escapeHtml(s) {
+ return String(s)
+ .replace(/&/g, "&")
+ .replace(//g, ">")
+ .replace(/"/g, """);
+}
+
+function marksWrap(text, marks) {
+ let html = escapeHtml(text);
+ for (const mark of marks || []) {
+ if (mark.type === "strong") html = `${html}`;
+ else if (mark.type === "em") html = `${html}`;
+ else if (mark.type === "underline") html = `${html}`;
+ else if (mark.type === "strike") html = `${html}`;
+ else if (mark.type === "code") html = `${html}`;
+ else if (mark.type === "link" && mark.attrs?.href) {
+ html = `${html}`;
+ }
+ }
+ return html;
+}
+
+function inlineHtml(node) {
+ if (!node) return "";
+ if (node.type === "text") return marksWrap(node.text || "", node.marks);
+ if (node.type === "hardBreak") return "
";
+ return (node.content || []).map(inlineHtml).join("");
+}
+
+function stableId(node, path) {
+ return (node.attrs && node.attrs.localId) || `adf-${path.replace(/\//g, "-")}`;
+}
+
+function ensureLocalIds(doc) {
+ const clone = JSON.parse(JSON.stringify(doc));
+ (clone.content || []).forEach((node, i) => {
+ node.attrs = node.attrs || {};
+ if (!node.attrs.localId) node.attrs.localId = `adf-${i}`;
+ });
+ return clone;
+}
+
+function blockToHtml(node, path) {
+ const id = stableId(node, path);
+ const attr = `data-chunk-id="${id}" data-adf-path="${path}" data-adf-type="${node.type}"`;
+
+ if (OPAQUE.has(node.type)) {
+ return `
[${node.type} preserved — not rewritten]
`;
+ }
+
+ if (node.type === "heading") {
+ const level = Math.min(Math.max(node.attrs?.level || 1, 1), 6);
+ return `${inlineHtml(node)}`;
+ }
+ if (node.type === "paragraph") {
+ return `${inlineHtml(node)}
`;
+ }
+ if (node.type === "blockquote") {
+ const inner = (node.content || []).map((c, i) => blockToHtml(c, `${path}/${i}`)).join("");
+ return `${inner}
`;
+ }
+ if (node.type === "bulletList" || node.type === "orderedList") {
+ const tag = node.type === "bulletList" ? "ul" : "ol";
+ const items = (node.content || [])
+ .map((li) => `${(li.content || []).map((c, i) => blockToHtml(c, `${path}-li-${i}`)).join("")}`)
+ .join("");
+ return `<${tag} ${attr}>${items}${tag}>`;
+ }
+ if (node.type === "rule") return `
`;
+ return `${escapeHtml(textOf(node))}
`;
+}
+
+function adfToHtml(doc) {
+ const rooted = ensureLocalIds(doc);
+ const html = (rooted.content || [])
+ .map((node, i) => blockToHtml(node, String(i)))
+ .join("\n");
+ return { html: `${html}
`, doc: rooted };
+}
+
+function listSections(doc) {
+ const rooted = ensureLocalIds(doc);
+ const sections = [];
+ let current = null;
+ (rooted.content || []).forEach((node, i) => {
+ if (node.type === "heading") {
+ current = {
+ id: stableId(node, String(i)),
+ heading: textOf(node) || "(untitled)",
+ level: node.attrs?.level || 1,
+ start: i,
+ end: i,
+ opaqueCount: 0,
+ };
+ sections.push(current);
+ return;
+ }
+ if (!current) {
+ current = {
+ id: `intro`,
+ heading: "(intro)",
+ level: 0,
+ start: i,
+ end: i,
+ opaqueCount: 0,
+ };
+ sections.push(current);
+ }
+ current.end = i;
+ if (OPAQUE.has(node.type)) current.opaqueCount += 1;
+ });
+ return { sections, doc: rooted };
+}
+
+function sliceSectionHtml(doc, start, end) {
+ const rooted = ensureLocalIds(doc);
+ const nodes = (rooted.content || []).slice(start, end + 1);
+ const html = nodes.map((node, i) => blockToHtml(node, String(start + i))).join("\n");
+ return { html: `${html}
`, doc: rooted };
+}
+
+function htmlFragmentToAdf(html) {
+ const root = parseHtml(`${html || ""}
`);
+ const out = [];
+ for (const el of root.firstChild?.childNodes || root.childNodes || []) {
+ const node = elementToAdf(el);
+ if (Array.isArray(node)) out.push(...node);
+ else if (node) out.push(node);
+ }
+ return out;
+}
+
+function textNodesFromEl(el) {
+ if (el.nodeType === 3) {
+ const t = (el.rawText || el.text || "").replace(/\s+/g, " ");
+ if (!t.trim()) return [];
+ return [{ type: "text", text: t }];
+ }
+ const tag = (el.rawTagName || "").toLowerCase();
+ if (!tag) {
+ return (el.childNodes || []).flatMap(textNodesFromEl);
+ }
+ if (tag === "br") return [{ type: "hardBreak" }];
+ const kids = (el.childNodes || []).flatMap(textNodesFromEl);
+ const mark =
+ tag === "strong" || tag === "b"
+ ? { type: "strong" }
+ : tag === "em" || tag === "i"
+ ? { type: "em" }
+ : tag === "u"
+ ? { type: "underline" }
+ : tag === "s" || tag === "strike"
+ ? { type: "strike" }
+ : tag === "code"
+ ? { type: "code" }
+ : tag === "a" && el.getAttribute?.("href")
+ ? { type: "link", attrs: { href: el.getAttribute("href") } }
+ : null;
+ if (!mark) return kids;
+ return kids.map((n) => {
+ if (n.type !== "text") return n;
+ return { ...n, marks: [...(n.marks || []), mark] };
+ });
+}
+
+function elementToAdf(el) {
+ if (!el || el.nodeType === 3) {
+ const t = (el?.rawText || el?.text || "").trim();
+ if (!t) return null;
+ return { type: "paragraph", content: [{ type: "text", text: t }] };
+ }
+ const tag = (el.rawTagName || "").toLowerCase();
+ if (el.getAttribute?.("data-adf-opaque") === "true") return null;
+ if (/^h[1-6]$/.test(tag)) {
+ return {
+ type: "heading",
+ attrs: { level: Number(tag[1]) },
+ content: textNodesFromEl(el),
+ };
+ }
+ if (tag === "p" || tag === "div") {
+ const content = textNodesFromEl(el);
+ if (!content.length) return { type: "paragraph", content: [] };
+ if (tag === "div") {
+ const kids = (el.childNodes || []).map(elementToAdf).filter(Boolean).flat();
+ if (kids.length) return kids;
+ }
+ return { type: "paragraph", content };
+ }
+ if (tag === "ul" || tag === "ol") {
+ return {
+ type: tag === "ul" ? "bulletList" : "orderedList",
+ content: (el.childNodes || [])
+ .filter((c) => (c.rawTagName || "").toLowerCase() === "li")
+ .map((li) => ({
+ type: "listItem",
+ content: [{ type: "paragraph", content: textNodesFromEl(li) }],
+ })),
+ };
+ }
+ if (tag === "blockquote") {
+ return {
+ type: "blockquote",
+ content: (el.childNodes || []).map(elementToAdf).filter(Boolean).flat(),
+ };
+ }
+ if (tag === "hr") return { type: "rule" };
+ const content = textNodesFromEl(el);
+ return content.length ? { type: "paragraph", content } : null;
+}
+
+function applyAcceptedChanges(originalDoc, changes) {
+ const doc = ensureLocalIds(originalDoc);
+ const byId = new Map();
+ (doc.content || []).forEach((node, i) => {
+ byId.set(stableId(node, String(i)), { node, index: i });
+ });
+
+ const next = doc.content.slice();
+ const sorted = [...changes].sort((a, b) => {
+ const ia = byId.get(a.chunk_id)?.index ?? 0;
+ const ib = byId.get(b.chunk_id)?.index ?? 0;
+ return ib - ia;
+ });
+
+ for (const change of sorted) {
+ if (change.approved === false) continue;
+ const loc = byId.get(change.chunk_id);
+ if (change.operation === "delete") {
+ if (!loc) continue;
+ if (OPAQUE.has(loc.node.type)) continue;
+ next.splice(loc.index, 1);
+ continue;
+ }
+ if (change.operation === "create") {
+ const frag = htmlFragmentToAdf(change.new_html || "");
+ const after = byId.get(change.insert_after_chunk_id);
+ const at = after ? after.index + 1 : next.length;
+ next.splice(at, 0, ...frag);
+ continue;
+ }
+ if (change.operation === "edit") {
+ if (!loc) continue;
+ if (OPAQUE.has(loc.node.type)) continue;
+ const frag = htmlFragmentToAdf(change.new_html || "");
+ if (!frag.length) continue;
+ next.splice(loc.index, 1, ...frag);
+ }
+ }
+
+ return { type: "doc", version: 1, content: next };
+}
+
+function isOpaque(type) {
+ return OPAQUE.has(type);
+}
+
+module.exports = {
+ OPAQUE,
+ isOpaque,
+ textOf,
+ ensureLocalIds,
+ adfToHtml,
+ listSections,
+ sliceSectionHtml,
+ htmlFragmentToAdf,
+ applyAcceptedChanges,
+ stableId,
+};
diff --git a/extensions/jaskiringg/confluence-ai/src/lib/adf.test.js b/extensions/jaskiringg/confluence-ai/src/lib/adf.test.js
new file mode 100644
index 0000000..6d24f0f
--- /dev/null
+++ b/extensions/jaskiringg/confluence-ai/src/lib/adf.test.js
@@ -0,0 +1,73 @@
+const { describe, it } = require("node:test");
+const assert = require("node:assert/strict");
+const fs = require("fs");
+const path = require("path");
+const {
+ adfToHtml,
+ listSections,
+ applyAcceptedChanges,
+ htmlFragmentToAdf,
+ isOpaque,
+} = require("./adf");
+const { normalizePendingChanges, parseTwice } = require("./superdocs");
+
+const demo = JSON.parse(
+ fs.readFileSync(path.join(__dirname, "../../fixtures/demo-page.adf.json"), "utf8")
+);
+
+describe("adf mapping", () => {
+ it("assigns chunk ids and keeps opaque placeholders", () => {
+ const { html, doc } = adfToHtml(demo);
+ assert.match(html, /data-chunk-id=/);
+ assert.match(html, /data-adf-opaque="true"/);
+ assert.equal(doc.content.some((n) => isOpaque(n.type)), true);
+ assert.doesNotMatch(html, /kubectl rollout undo/);
+ assert.doesNotMatch(html, /Order API/);
+ });
+
+ it("lists heading sections", () => {
+ const { sections } = listSections(demo);
+ assert.ok(sections.length >= 3);
+ assert.equal(sections[0].heading.includes("IT Change Policy"), true);
+ });
+
+ it("writeback replaces only accepted non-opaque blocks", () => {
+ const { doc } = adfToHtml(demo);
+ const freeze = doc.content.find((n) => n.type === "paragraph" && JSON.stringify(n).includes("02:00"));
+ const table = doc.content.find((n) => n.type === "table");
+ const next = applyAcceptedChanges(doc, [
+ {
+ operation: "edit",
+ chunk_id: freeze.attrs.localId,
+ new_html: "North region freeze is 01:00-05:00 IST on release nights.
",
+ approved: true,
+ },
+ {
+ operation: "delete",
+ chunk_id: table.attrs.localId,
+ approved: true,
+ },
+ ]);
+ const texts = JSON.stringify(next);
+ assert.match(texts, /01:00-05:00/);
+ assert.equal(next.content.some((n) => n.type === "table"), true);
+ assert.equal(next.content.some((n) => n.type === "codeBlock"), true);
+ });
+
+ it("html fragment becomes heading + paragraph", () => {
+ const frag = htmlFragmentToAdf("Purpose
Not stated in source.
");
+ assert.equal(frag[0].type, "heading");
+ assert.equal(frag[1].type, "paragraph");
+ });
+});
+
+describe("superdocs pending parse", () => {
+ it("parses twice when payload is a JSON string", () => {
+ const inner = [{ change_id: "ch_1", operation: "edit", chunk_id: "a", old_html: "x
", new_html: "y
" }];
+ const wrapped = JSON.stringify(JSON.stringify(inner));
+ const once = parseTwice(JSON.parse(wrapped));
+ assert.equal(Array.isArray(once), true);
+ const pending = normalizePendingChanges(JSON.stringify(inner));
+ assert.equal(pending[0].change_id, "ch_1");
+ });
+});
diff --git a/extensions/jaskiringg/confluence-ai/src/lib/confluence.js b/extensions/jaskiringg/confluence-ai/src/lib/confluence.js
new file mode 100644
index 0000000..7804450
--- /dev/null
+++ b/extensions/jaskiringg/confluence-ai/src/lib/confluence.js
@@ -0,0 +1,209 @@
+const fs = require("fs");
+const path = require("path");
+
+const DEMO_PAGE_ID = "demo-page";
+const DEMO_SPACE_ID = "demo-space";
+
+function loadDemoAdf() {
+ const p = path.join(__dirname, "../../fixtures/demo-page.adf.json");
+ return JSON.parse(fs.readFileSync(p, "utf8"));
+}
+
+function authHeader(email, token) {
+ return `Basic ${Buffer.from(`${email}:${token}`).toString("base64")}`;
+}
+
+function confluenceConfigured(env) {
+ return !!(env.CONFLUENCE_BASE_URL && env.CONFLUENCE_EMAIL && env.CONFLUENCE_API_TOKEN);
+}
+
+async function cfFetch(env, pathname, { method = "GET", body } = {}) {
+ const url = `${env.CONFLUENCE_BASE_URL.replace(/\/$/, "")}${pathname}`;
+ const res = await fetch(url, {
+ method,
+ headers: {
+ Authorization: authHeader(env.CONFLUENCE_EMAIL, env.CONFLUENCE_API_TOKEN),
+ Accept: "application/json",
+ ...(body ? { "Content-Type": "application/json" } : {}),
+ },
+ body: body ? JSON.stringify(body) : undefined,
+ });
+ const text = await res.text();
+ const data = text ? JSON.parse(text) : null;
+ if (!res.ok) {
+ throw new Error(`Confluence ${res.status}: ${text.slice(0, 500)}`);
+ }
+ return data;
+}
+
+function parseAdfFromPage(page) {
+ const value =
+ page?.body?.atlas_doc_format?.value ||
+ page?.body?.value ||
+ null;
+ if (!value) throw new Error("Page has no atlas_doc_format body");
+ return typeof value === "string" ? JSON.parse(value) : value;
+}
+
+const DEMO_TITLES = {
+ [DEMO_PAGE_ID]: "IT Change Policy — North Region",
+ "demo-page-2": "Go-live checklist (draft)",
+ "demo-page-3": "SIT notes — week 12",
+};
+
+async function getPage(env, pageId) {
+ if (!confluenceConfigured(env) || String(pageId).startsWith("demo")) {
+ const adf = loadDemoAdf();
+ if (pageId === "demo-page-2") {
+ adf.content[0] = {
+ type: "heading",
+ attrs: { level: 1 },
+ content: [{ type: "text", text: "Go-live checklist (draft)" }],
+ };
+ }
+ if (pageId === "demo-page-3") {
+ adf.content[0] = {
+ type: "heading",
+ attrs: { level: 1 },
+ content: [{ type: "text", text: "SIT notes — week 12" }],
+ };
+ }
+ return {
+ id: String(pageId),
+ title: DEMO_TITLES[pageId] || "Demo page",
+ status: "current",
+ spaceId: DEMO_SPACE_ID,
+ version: { number: 1 },
+ adf,
+ demo: true,
+ };
+ }
+ const page = await cfFetch(
+ env,
+ `/wiki/api/v2/pages/${encodeURIComponent(pageId)}?body-format=atlas_doc_format`
+ );
+ return {
+ id: String(page.id),
+ title: page.title,
+ status: page.status,
+ spaceId: String(page.spaceId),
+ version: page.version,
+ adf: parseAdfFromPage(page),
+ demo: false,
+ };
+}
+
+async function updatePage(env, { pageId, title, adf, versionNumber, message }) {
+ if (!confluenceConfigured(env) || String(pageId).startsWith("demo")) {
+ return {
+ id: DEMO_PAGE_ID,
+ title,
+ version: { number: (versionNumber || 1) + 1, message },
+ demo: true,
+ adf,
+ };
+ }
+ return cfFetch(env, `/wiki/api/v2/pages/${encodeURIComponent(pageId)}`, {
+ method: "PUT",
+ body: {
+ id: String(pageId),
+ status: "current",
+ title,
+ body: {
+ representation: "atlas_doc_format",
+ value: JSON.stringify(adf),
+ },
+ version: {
+ number: versionNumber + 1,
+ message: message || "SuperDocs AI edit (approved batch)",
+ },
+ },
+ });
+}
+
+async function listSpacePages(env, spaceId) {
+ if (!confluenceConfigured(env) || spaceId === DEMO_SPACE_ID) {
+ return [
+ { id: DEMO_PAGE_ID, title: "IT Change Policy — North Region", status: "current" },
+ { id: "demo-page-2", title: "Go-live checklist (draft)", status: "current" },
+ { id: "demo-page-3", title: "SIT notes — week 12", status: "current" },
+ ];
+ }
+ const data = await cfFetch(
+ env,
+ `/wiki/api/v2/spaces/${encodeURIComponent(spaceId)}/pages?limit=50&status=current`
+ );
+ return (data.results || []).map((p) => ({
+ id: String(p.id),
+ title: p.title,
+ status: p.status,
+ }));
+}
+
+async function searchUsers(env, query) {
+ if (!confluenceConfigured(env)) {
+ return [
+ { accountId: "demo-approver", displayName: "Priya Sharma (demo)", email: "priya@example.com" },
+ { accountId: "demo-author", displayName: "Jaskirat Singh (demo)", email: "singhjass6404@gmail.com" },
+ ].filter((u) => !query || u.displayName.toLowerCase().includes(query.toLowerCase()));
+ }
+ const data = await cfFetch(
+ env,
+ `/wiki/rest/api/search/user?cql=${encodeURIComponent(`user.fullname~"${query}"`)}&limit=10`
+ );
+ return (data.results || []).map((r) => ({
+ accountId: r.user?.accountId,
+ displayName: r.user?.displayName,
+ email: r.user?.email,
+ }));
+}
+
+const PROPERTY_KEY = "superdocs-review";
+
+async function getReviewState(env, pageId, memory) {
+ if (memory.has(pageId)) return memory.get(pageId);
+ if (!confluenceConfigured(env) || pageId === DEMO_PAGE_ID) {
+ return { status: "draft" };
+ }
+ try {
+ const data = await cfFetch(
+ env,
+ `/wiki/rest/api/content/${encodeURIComponent(pageId)}/property/${PROPERTY_KEY}`
+ );
+ return data.value || { status: "draft" };
+ } catch {
+ return { status: "draft" };
+ }
+}
+
+async function setReviewState(env, pageId, value, memory) {
+ memory.set(pageId, value);
+ if (!confluenceConfigured(env) || pageId === DEMO_PAGE_ID) return value;
+ try {
+ await cfFetch(
+ env,
+ `/wiki/rest/api/content/${encodeURIComponent(pageId)}/property/${PROPERTY_KEY}`,
+ { method: "PUT", body: { key: PROPERTY_KEY, value } }
+ );
+ } catch {
+ await cfFetch(
+ env,
+ `/wiki/rest/api/content/${encodeURIComponent(pageId)}/property`,
+ { method: "POST", body: { key: PROPERTY_KEY, value } }
+ );
+ }
+ return value;
+}
+
+module.exports = {
+ DEMO_PAGE_ID,
+ DEMO_SPACE_ID,
+ confluenceConfigured,
+ getPage,
+ updatePage,
+ listSpacePages,
+ searchUsers,
+ getReviewState,
+ setReviewState,
+ loadDemoAdf,
+};
diff --git a/extensions/jaskiringg/confluence-ai/src/lib/superdocs.js b/extensions/jaskiringg/confluence-ai/src/lib/superdocs.js
new file mode 100644
index 0000000..597badd
--- /dev/null
+++ b/extensions/jaskiringg/confluence-ai/src/lib/superdocs.js
@@ -0,0 +1,158 @@
+const DEFAULT_BASE = process.env.SUPERDOCS_API_BASE || "https://api.superdocs.app";
+
+function parseJsonLenient(text) {
+ if (text == null) return null;
+ if (typeof text !== "string") return text;
+ const cleaned = text.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F]/g, " ");
+ try {
+ return JSON.parse(cleaned);
+ } catch (err) {
+ const once = cleaned.replace(/,\s*([}\]])/g, "$1");
+ return JSON.parse(once);
+ }
+}
+
+function parseTwice(value) {
+ let v = value;
+ if (typeof v === "string") v = parseJsonLenient(v);
+ if (typeof v === "string") v = parseJsonLenient(v);
+ return v;
+}
+
+function normalizePendingChanges(raw) {
+ let v = parseTwice(raw);
+ if (!v) return [];
+ if (!Array.isArray(v) && v.changes) v = parseTwice(v.changes);
+ if (!Array.isArray(v)) return [];
+ return v.map((item) => {
+ const c = typeof item === "string" ? parseTwice(item) : item;
+ return c && typeof c === "object" ? c : null;
+ }).filter(Boolean);
+}
+
+async function sdFetch(path, { method = "GET", body, apiKey, base = DEFAULT_BASE } = {}) {
+ if (!apiKey) throw new Error("SUPERDOCS_API_KEY is missing");
+ const res = await fetch(`${base}${path}`, {
+ method,
+ headers: {
+ Authorization: `Bearer ${apiKey}`,
+ ...(body ? { "Content-Type": "application/json" } : {}),
+ },
+ body: body ? JSON.stringify(body) : undefined,
+ });
+ const text = await res.text();
+ const data = text ? parseJsonLenient(text) : null;
+ if (!res.ok) {
+ const msg = data?.detail || data?.error || data?.message || text.slice(0, 400);
+ const err = new Error(`SuperDocs ${res.status}: ${typeof msg === "string" ? msg : JSON.stringify(msg)}`);
+ err.status = res.status;
+ err.body = data;
+ throw err;
+ }
+ return data;
+}
+
+async function startHitlEdit({ apiKey, sessionId, documentHtml, message, modelTier = "core" }) {
+ return sdFetch("/v1/chat/async", {
+ method: "POST",
+ apiKey,
+ body: {
+ message,
+ session_id: sessionId,
+ document_html: documentHtml,
+ approval_mode: "ask_every_time",
+ model_tier: modelTier,
+ },
+ });
+}
+
+async function getJob({ apiKey, jobId }) {
+ const job = await sdFetch(`/v1/jobs/${jobId}`, { apiKey });
+ const pending = normalizePendingChanges(job?.metadata?.pending_changes);
+ return { ...job, pending_changes: pending };
+}
+
+async function approveChanges({ apiKey, sessionId, jobId, decisions }) {
+ const changes = decisions.map((d) => ({
+ change_id: d.change_id,
+ approved: !!d.approved,
+ ...(d.feedback ? { feedback: d.feedback } : {}),
+ }));
+ return sdFetch(`/v1/chat/${encodeURIComponent(sessionId)}/approve`, {
+ method: "POST",
+ apiKey,
+ body: {
+ job_id: jobId,
+ approved: true,
+ changes,
+ },
+ });
+}
+
+async function continueJob({ apiKey, sessionId, jobId, shouldContinue }) {
+ return sdFetch(`/v1/chat/${encodeURIComponent(sessionId)}/continue`, {
+ method: "POST",
+ apiKey,
+ body: { job_id: jobId, continue: !!shouldContinue },
+ });
+}
+
+async function exportPdf({ apiKey, sessionId, html, filename = "confluence-export" }) {
+ const res = await fetch(`${DEFAULT_BASE}/v1/documents/export`, {
+ method: "POST",
+ headers: {
+ Authorization: `Bearer ${apiKey}`,
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ session_id: html ? undefined : sessionId,
+ html: html || undefined,
+ format: "pdf",
+ options: { filename, paper_size: "A4", margins: "normal" },
+ }),
+ });
+ if (!res.ok) {
+ const text = await res.text();
+ throw new Error(`Export failed ${res.status}: ${text.slice(0, 400)}`);
+ }
+ const buf = Buffer.from(await res.arrayBuffer());
+ return {
+ filename: `${filename}.pdf`,
+ contentType: res.headers.get("content-type") || "application/pdf",
+ base64: buf.toString("base64"),
+ bytes: buf.length,
+ };
+}
+
+const HOUSE_STRUCTURE_PROMPT = `Normalise this Confluence page to the house structure below.
+Keep every fact that is already in the page. Do not invent owners, dates, metrics, or systems.
+Do not rewrite or remove blocks marked as preserved (tables, code, macros).
+Use these headings, in this order, and move existing sentences under the right heading:
+
+#
+## Purpose
+## Current status
+## Decisions
+## Open questions
+## Next steps
+
+If a heading has no supporting source text, write exactly: "Not stated in source."`;
+
+const LETTERHEAD = `
+
Internal · implementation pack
+
House document export
+
Not for external distribution · generated via SuperDocs
+
`;
+
+module.exports = {
+ parseJsonLenient,
+ parseTwice,
+ normalizePendingChanges,
+ startHitlEdit,
+ getJob,
+ approveChanges,
+ continueJob,
+ exportPdf,
+ HOUSE_STRUCTURE_PROMPT,
+ LETTERHEAD,
+};
diff --git a/extensions/jaskiringg/confluence-ai/static/ui/index.html b/extensions/jaskiringg/confluence-ai/static/ui/index.html
new file mode 100644
index 0000000..7288b80
--- /dev/null
+++ b/extensions/jaskiringg/confluence-ai/static/ui/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ SuperDocs AI edit — Confluence
+
+
+
+
+
+
diff --git a/extensions/jaskiringg/confluence-ai/static/ui/package-lock.json b/extensions/jaskiringg/confluence-ai/static/ui/package-lock.json
new file mode 100644
index 0000000..afa45c4
--- /dev/null
+++ b/extensions/jaskiringg/confluence-ai/static/ui/package-lock.json
@@ -0,0 +1,1857 @@
+{
+ "name": "confluence-ai-ui",
+ "version": "0.1.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "confluence-ai-ui",
+ "version": "0.1.0",
+ "dependencies": {
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-react": "^4.3.4",
+ "vite": "^6.0.7"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
+ "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.29.7",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz",
+ "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz",
+ "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/generator": "^7.29.7",
+ "@babel/helper-compilation-targets": "^7.29.7",
+ "@babel/helper-module-transforms": "^7.29.7",
+ "@babel/helpers": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/template": "^7.29.7",
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz",
+ "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.8",
+ "@babel/types": "^7.29.8",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz",
+ "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.29.7",
+ "@babel/helper-validator-option": "^7.29.7",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz",
+ "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz",
+ "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz",
+ "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7",
+ "@babel/traverse": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
+ "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
+ "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
+ "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz",
+ "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz",
+ "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz",
+ "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.8"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz",
+ "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz",
+ "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz",
+ "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz",
+ "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/generator": "^7.29.8",
+ "@babel/helper-globals": "^7.29.7",
+ "@babel/parser": "^7.29.8",
+ "@babel/template": "^7.29.7",
+ "@babel/types": "^7.29.8",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz",
+ "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
+ "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
+ "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
+ "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
+ "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
+ "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
+ "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
+ "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
+ "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
+ "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
+ "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
+ "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
+ "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
+ "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
+ "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
+ "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
+ "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
+ "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
+ "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
+ "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
+ "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
+ "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
+ "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
+ "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@napi-rs/lzma-linux-x64-gnu": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz",
+ "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^22.20 || ^24.12 || >=25"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-beta.27",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
+ "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.62.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.4.tgz",
+ "integrity": "sha512-RrPokAb7dmbxFoeO3TloqHyOjgye8RkBhSqmp4aJMIex4c9r46ZstPnleDQOq1t46VOVjwIuwNogIqbodV1Vvg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.62.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.4.tgz",
+ "integrity": "sha512-JKuJc+pnpks2pjy7L/N3v/cAkZxYlnmuZoD840ldbMI5KDbC4iO9NKwPKYdjYFCMAIIlBzYSFHxIJVYzRo2/8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.62.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.4.tgz",
+ "integrity": "sha512-krw5uS2STmvJ02x0uTXHbqQNuz+9eZ1iw+qXk9dmW2gvV4jV7O2hEoOnuhFrpOPiel1mBFtqbxYZZtC46hXLOw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.62.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.4.tgz",
+ "integrity": "sha512-wsTxtgApb4PrOsNJIm0FZ1h3WvCC+k9uxLJ4ad75hgoS4NiRes2SoJFlDAyMwiUY8IssDqGcHbXuN0sx1tfF1A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.62.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.4.tgz",
+ "integrity": "sha512-GUOnQlyZe3yAXhWOtOMsn5Qkrv5E5mZXa0thbARWi5Ei2szlVXJFQhddZ4HbAzh8q92w5twp+CQvs/eFanz9YQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.62.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.4.tgz",
+ "integrity": "sha512-/Y7f3QuxjzPKsjA/rfEDa3+0vXqyjmJ50Ln8dPpCmWkKTrUoWHG1cWhTqaAMLob2m2nESWuC7yGrREz019Ztqg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.62.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.4.tgz",
+ "integrity": "sha512-81wiiX3v7aqy+T+bT61TJ78yJjRquqFFTTbAPt08imfQQzkPIW8t6aJbkTagtCCrXMNc9D66+geqlK7ydLPNqA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.62.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.4.tgz",
+ "integrity": "sha512-9kmDIvNZqdoHOBZgNtpTBeLWYO/LVipM3H/j62P8848/l/VPEQL6N3uxU9pvP1oZAsXyC2MEnFP3ovRjo7WYNQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.62.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.4.tgz",
+ "integrity": "sha512-CcnXHWnXg69g+DX5VWL3FHts3qMRN2uVEHX+BZvGLdd07/gXkn3ePjYtO1LDJvxkGKVHMclKBRa1QUTH+6toYQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.62.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.4.tgz",
+ "integrity": "sha512-iFOibiHnTRuhrWLlRsOQFdZJJIa7S8OwkneJr4ocALP16u5yk6lWLINFwhHaEqBFMsKDUZofLkGos7+CPzGB3g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.62.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.4.tgz",
+ "integrity": "sha512-XnWYMI7euHlb5a871xPja+Gm7DRCFU+FGRrtS2sMq9N8FvqtpagUy6gD4YOemC5MRk9xbh8+jYMEJbigFQwsgA==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.62.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.4.tgz",
+ "integrity": "sha512-qGDAlO0U8xedCcsdRm9oaoQY8DAx/QT7uIxJWhCdx0ceIWX783UC9QSYkdpzAe29wNiVfp24+bZdQmn49o45SQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.62.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.4.tgz",
+ "integrity": "sha512-ru4H6ezD7ysA5EiEK6qkkaEb4modH8CTej6kUy/gQi20u3kB3G7Zn8snXXkeJSCOFKG/rbPPtM/+9Wgas1961w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.62.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.4.tgz",
+ "integrity": "sha512-2W4MO5WQVJnbJaZdvDb9rhBDuFU1nKIepPFpJUBsTh2k1YY2g+ODViaWuyOAjQ5cOP7NvrvLzt3wvHOoiAvc7w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.62.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.4.tgz",
+ "integrity": "sha512-+fxjfuoAmVMCYV5QyjoIpu0cp5DOiOTeqYFk1AVaxGr+/ravWLX89XfQmptsoWcaVy/TGf2hexzbUOrCQIL1CQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.62.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.4.tgz",
+ "integrity": "sha512-jTn8JfHGL4djjFxPuM06LmNUJDsst2jeVlsd9OmIH6zc5sC9K6rIuO4YajXatLUpBmBKl6b35ro1QZocLi+tcA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.62.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.4.tgz",
+ "integrity": "sha512-oCJCJL4pXsoDcP2QZ+JVlPTIRc6266zsIaeJJsWImmF7HO0W8nb6HuSgZlMWxJwaPf8ehbSw8yo0EUw925hKsA==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.62.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.4.tgz",
+ "integrity": "sha512-W69hukhZ3KKNRCaMIEzKvcFye42hh0FE1+YoYaf5+Ikacuftoco6yO/xouz0hc5d5W/s3yBro5jRiuEE/Q5vUw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.62.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.4.tgz",
+ "integrity": "sha512-qiXbGG2jkjXhzXpsFZSR2Xpb8DN/UaxYsbb/STbuR/6fpaDgRmmaq1B/LmtF2wQFOFOSsK2jdE0RZ3a0zHn4QA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.62.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.4.tgz",
+ "integrity": "sha512-nWeM//hxv8mIo6jD7Hu4o48DVmV9pbV6gsKaWU+4NFyqHoPKwrkRiZGLKUhOBk8qNmDmpwFtPKg80Bo/Tn4xiQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.62.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.4.tgz",
+ "integrity": "sha512-s62SQ/vgsRSvMwDkOEfTqfgASF0f26ZNaQuTA6Aok5lrikf89yI2W0gFHvZb2Jpgc6N8JnOKZgCK2iciO3CsxQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.62.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.4.tgz",
+ "integrity": "sha512-J6wGf8TVGbXJq+HH+ttTvrcfNKPbuZecV6KT1B8I18BC5IURUh5kl4Yl5OEP5eFIUoI5BWxCsyYMhFsDx8kekw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.62.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.4.tgz",
+ "integrity": "sha512-zmfrQd/0wu6oJs8Vq8KwY/YtsKSsLtKe/HwAP4Wqy8LhWjeT55fHRAkOhYQ12wI3ayS4Tt12d5CDRD7N96SAYQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.62.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.4.tgz",
+ "integrity": "sha512-qPzHqdj9rfUD+w79dtE07zi/kFwKyCJqplp5K5ygeLTp7jLpAoc16OAH39HSmRC9UpozaecsleI8uAdEj6v2yw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.62.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.4.tgz",
+ "integrity": "sha512-zD6NdeWEByGE9QF9vCrlJ5YQB4oq9q91kPZS37Jwj5hOkvR1lTBSpsKhKDw4IJtbQ35LsTS1HD9DZYGKIshU1Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
+ "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
+ "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.28.0",
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
+ "@rolldown/pluginutils": "1.0.0-beta.27",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.17.0"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
+ }
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.11.13",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.13.tgz",
+ "integrity": "sha512-k9HNuUVMlqVjQ9UHzfPjIqiDbWw7WqT1AoT7GL8VwvF3r0ZfArtgiSPAlmupyNquNgOJHTuH4CKYf8ttMTWBTQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.cjs"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.8",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz",
+ "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "baseline-browser-mapping": "^2.11.12",
+ "caniuse-lite": "^1.0.30001809",
+ "electron-to-chromium": "^1.5.402",
+ "node-releases": "^2.0.53",
+ "update-browserslist-db": "^1.3.0"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001809",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001809.tgz",
+ "integrity": "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.403",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.403.tgz",
+ "integrity": "sha512-MQsYmdaLzvaCX5j+ZZBr5Fm6uCCnPQcRtlvmvRlWqrXy+BH2O4ffXIAScF+JQznQWB9brWp4lSD9Z4yNmaf2BA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/esbuild": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
+ "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.25.12",
+ "@esbuild/android-arm": "0.25.12",
+ "@esbuild/android-arm64": "0.25.12",
+ "@esbuild/android-x64": "0.25.12",
+ "@esbuild/darwin-arm64": "0.25.12",
+ "@esbuild/darwin-x64": "0.25.12",
+ "@esbuild/freebsd-arm64": "0.25.12",
+ "@esbuild/freebsd-x64": "0.25.12",
+ "@esbuild/linux-arm": "0.25.12",
+ "@esbuild/linux-arm64": "0.25.12",
+ "@esbuild/linux-ia32": "0.25.12",
+ "@esbuild/linux-loong64": "0.25.12",
+ "@esbuild/linux-mips64el": "0.25.12",
+ "@esbuild/linux-ppc64": "0.25.12",
+ "@esbuild/linux-riscv64": "0.25.12",
+ "@esbuild/linux-s390x": "0.25.12",
+ "@esbuild/linux-x64": "0.25.12",
+ "@esbuild/netbsd-arm64": "0.25.12",
+ "@esbuild/netbsd-x64": "0.25.12",
+ "@esbuild/openbsd-arm64": "0.25.12",
+ "@esbuild/openbsd-x64": "0.25.12",
+ "@esbuild/openharmony-arm64": "0.25.12",
+ "@esbuild/sunos-x64": "0.25.12",
+ "@esbuild/win32-arm64": "0.25.12",
+ "@esbuild/win32-ia32": "0.25.12",
+ "@esbuild/win32-x64": "0.25.12"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "license": "MIT"
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.18",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz",
+ "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.53",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.53.tgz",
+ "integrity": "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
+ "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.26",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz",
+ "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.17",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/react": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.2"
+ },
+ "peerDependencies": {
+ "react": "^18.3.1"
+ }
+ },
+ "node_modules/react-refresh": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
+ "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.62.4",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.4.tgz",
+ "integrity": "sha512-RXOqwaPsBGjMNMa4sQjDjHieHEZDFoj/Rdr46l2MU5DfEs16wHJPC2RPTPHWhNl+M3aI472LLqFkFKut4SblOg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.9"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@napi-rs/lzma-linux-x64-gnu": "1.5.1",
+ "@rollup/rollup-android-arm-eabi": "4.62.4",
+ "@rollup/rollup-android-arm64": "4.62.4",
+ "@rollup/rollup-darwin-arm64": "4.62.4",
+ "@rollup/rollup-darwin-x64": "4.62.4",
+ "@rollup/rollup-freebsd-arm64": "4.62.4",
+ "@rollup/rollup-freebsd-x64": "4.62.4",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.62.4",
+ "@rollup/rollup-linux-arm-musleabihf": "4.62.4",
+ "@rollup/rollup-linux-arm64-gnu": "4.62.4",
+ "@rollup/rollup-linux-arm64-musl": "4.62.4",
+ "@rollup/rollup-linux-loong64-gnu": "4.62.4",
+ "@rollup/rollup-linux-loong64-musl": "4.62.4",
+ "@rollup/rollup-linux-ppc64-gnu": "4.62.4",
+ "@rollup/rollup-linux-ppc64-musl": "4.62.4",
+ "@rollup/rollup-linux-riscv64-gnu": "4.62.4",
+ "@rollup/rollup-linux-riscv64-musl": "4.62.4",
+ "@rollup/rollup-linux-s390x-gnu": "4.62.4",
+ "@rollup/rollup-linux-x64-gnu": "4.62.4",
+ "@rollup/rollup-linux-x64-musl": "4.62.4",
+ "@rollup/rollup-openbsd-x64": "4.62.4",
+ "@rollup/rollup-openharmony-arm64": "4.62.4",
+ "@rollup/rollup-win32-arm64-msvc": "4.62.4",
+ "@rollup/rollup-win32-ia32-msvc": "4.62.4",
+ "@rollup/rollup-win32-x64-gnu": "4.62.4",
+ "@rollup/rollup-win32-x64-msvc": "4.62.4",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.23.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.17",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
+ "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.0.tgz",
+ "integrity": "sha512-x/M6q3w4Ybp91CNaS4S69UnliqR3BzRpOT6LWbksjth0S/+jhfaPJsWjt/TewpT8j9eLIojUf5jr29WextHroA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/vite": {
+ "version": "6.4.3",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz",
+ "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.25.0",
+ "fdir": "^6.4.4",
+ "picomatch": "^4.0.2",
+ "postcss": "^8.5.3",
+ "rollup": "^4.34.9",
+ "tinyglobby": "^0.2.13"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
+ "jiti": ">=1.21.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ }
+ }
+}
diff --git a/extensions/jaskiringg/confluence-ai/static/ui/package.json b/extensions/jaskiringg/confluence-ai/static/ui/package.json
new file mode 100644
index 0000000..defea1a
--- /dev/null
+++ b/extensions/jaskiringg/confluence-ai/static/ui/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "confluence-ai-ui",
+ "private": true,
+ "version": "0.1.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build"
+ },
+ "dependencies": {
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-react": "^4.3.4",
+ "vite": "^6.0.7"
+ }
+}
diff --git a/extensions/jaskiringg/confluence-ai/static/ui/src/App.jsx b/extensions/jaskiringg/confluence-ai/static/ui/src/App.jsx
new file mode 100644
index 0000000..8afb9e5
--- /dev/null
+++ b/extensions/jaskiringg/confluence-ai/static/ui/src/App.jsx
@@ -0,0 +1,316 @@
+import { useEffect, useMemo, useState } from "react";
+import { call } from "./api.js";
+
+function diffPreview(oldHtml, newHtml) {
+ return { oldHtml: oldHtml || "", newHtml: newHtml || "" };
+}
+
+export default function App() {
+ const [ctx, setCtx] = useState(null);
+ const [err, setErr] = useState("");
+ const [busy, setBusy] = useState(false);
+ const [section, setSection] = useState(null);
+ const [message, setMessage] = useState("Change the freeze window to 01:00-05:00 IST. Do not invent other policy.");
+ const [job, setJob] = useState(null);
+ const [pending, setPending] = useState([]);
+ const [decisions, setDecisions] = useState({});
+ const [role, setRole] = useState("author");
+ const [approver, setApprover] = useState(null);
+ const [users, setUsers] = useState([]);
+ const [queue, setQueue] = useState([]);
+ const [tab, setTab] = useState("edit");
+ const [note, setNote] = useState("");
+
+ const pageId = ctx?.page?.id || "demo-page";
+ const spaceId = ctx?.page?.spaceId || "demo-space";
+
+ async function refresh() {
+ const data = await call("getContext", { pageId, spaceId });
+ setCtx(data);
+ if (!section && data.sections?.[0]) setSection(data.sections[0]);
+ }
+
+ useEffect(() => {
+ refresh().catch((e) => setErr(e.message));
+ call("listPages", { spaceId: "demo-space" })
+ .then((d) => setQueue(d.pages || []))
+ .catch(() => {});
+ call("findUsers", { query: "" })
+ .then((d) => setUsers(d.users || []))
+ .catch(() => {});
+ }, []);
+
+ async function pollUntilSettled(jobId, sessionId) {
+ for (let i = 0; i < 40; i += 1) {
+ const p = await call("pollJob", { jobId, sessionId });
+ if (p.status === "awaiting_approval" && p.awaitingKind === "continue_prompt") {
+ await call("resumeLargeEdit", { jobId, sessionId, continue: true });
+ await new Promise((r) => setTimeout(r, 2000));
+ continue;
+ }
+ if (p.status === "awaiting_approval") {
+ const changes = p.pendingChanges || [];
+ setPending(changes);
+ const next = {};
+ for (const c of changes) next[c.change_id] = true;
+ setDecisions(next);
+ return p;
+ }
+ if (p.status === "completed" || p.status === "failed") return p;
+ await new Promise((r) => setTimeout(r, 2000));
+ }
+ throw new Error("Timed out waiting for SuperDocs. Large edits can take a few minutes — try again.");
+ }
+
+ async function runEdit(opts = {}) {
+ setErr("");
+ setNote("");
+ setBusy(true);
+ try {
+ const payload = {
+ pageId,
+ message,
+ house: !!opts.house,
+ role,
+ modelTier: "core",
+ };
+ if (section && !opts.wholePage && !opts.house) {
+ payload.start = section.start;
+ payload.end = section.end;
+ }
+ const started = await call("startEdit", payload);
+ setJob(started);
+ const settled = await pollUntilSettled(started.jobId, started.sessionId);
+ if (settled.status === "failed") throw new Error(settled.error || "Job failed");
+ setNote(settled.status === "completed" && !settled.pendingChanges?.length
+ ? "Job finished with no pending diffs."
+ : `${(settled.pendingChanges || pending).length || "Pending"} change(s) to review.`);
+ } catch (e) {
+ setErr(e.message);
+ } finally {
+ setBusy(false);
+ }
+ }
+
+ async function submitDecisions() {
+ if (!job) return;
+ setBusy(true);
+ setErr("");
+ try {
+ const list = pending.map((c) => ({
+ change_id: c.change_id,
+ chunk_id: c.chunk_id,
+ operation: c.operation,
+ new_html: c.new_html,
+ old_html: c.old_html,
+ insert_after_chunk_id: c.insert_after_chunk_id,
+ approved: decisions[c.change_id] !== false,
+ }));
+ await call("decide", { sessionId: job.sessionId, jobId: job.jobId, decisions: list });
+ if (approver && role === "author") {
+ await call("sendToApprover", {
+ pageId,
+ approverAccountId: approver.accountId,
+ approverName: approver.displayName,
+ sessionId: job.sessionId,
+ jobId: job.jobId,
+ });
+ setNote(`Sent to ${approver.displayName}. Write-back is blocked until they accept.`);
+ await refresh();
+ return;
+ }
+ const written = await call("writeBack", {
+ sessionId: job.sessionId,
+ pageId,
+ decisions: list,
+ role,
+ actorAccountId: role === "approver" ? approver?.accountId : "demo-author",
+ });
+ setNote(`Wrote accepted ranges only. Confluence version ${written.version}${written.demo ? " (demo page)" : ""}.`);
+ setPending([]);
+ await refresh();
+ } catch (e) {
+ setErr(e.message);
+ } finally {
+ setBusy(false);
+ }
+ }
+
+ async function downloadPdf() {
+ setBusy(true);
+ setErr("");
+ try {
+ const pdf = await call("exportBrandedPdf", { pageId, sessionId: job?.sessionId });
+ const bin = atob(pdf.base64);
+ const bytes = new Uint8Array(bin.length);
+ for (let i = 0; i < bin.length; i += 1) bytes[i] = bin.charCodeAt(i);
+ const blob = new Blob([bytes], { type: pdf.contentType });
+ const a = document.createElement("a");
+ a.href = URL.createObjectURL(blob);
+ a.download = pdf.filename;
+ a.click();
+ setNote(`Exported ${pdf.filename} (${pdf.bytes} bytes).`);
+ } catch (e) {
+ setErr(e.message);
+ } finally {
+ setBusy(false);
+ }
+ }
+
+ const blocked = ctx?.review?.status === "awaiting_approver";
+
+ const queueView = useMemo(
+ () => queue.map((p) => ({ ...p, current: p.id === pageId })),
+ [queue, pageId]
+ );
+
+ if (!ctx) {
+ return ;
+ }
+
+ return (
+
+
+
+ {err ?
{err}
: null}
+ {note ?
{note}
: null}
+ {blocked ? (
+
+ Publish / write-back blocked until {ctx.review.approverName || "named approver"} accepts.
+ Switch role to Approver to finish.
+
+ ) : null}
+
+ {tab === "space" ? (
+
+ Normalise space — page by page
+
+ House structure is applied one page at a time. Nothing is bulk-overwritten.
+ Open a page, review diffs, then move to the next.
+
+
+ {queueView.map((p) => (
+ -
+
+
+ ))}
+
+
+
+ ) : (
+
+
+ Section
+
+ {(ctx.sections || []).map((s) => (
+ -
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+ Review
+
+
+
+
+
+ {!pending.length ? (
+ No pending diffs. Select a section and send an instruction.
+ ) : (
+ pending.map((c) => {
+ const d = diffPreview(c.old_html, c.new_html);
+ const accepted = decisions[c.change_id] !== false;
+ return (
+
+
+ {c.operation || "edit"}
+ {c.ai_explanation || ""}
+
+
+
create" }} />
+
delete" }} />
+
+
+
+
+
+
+ );
+ })
+ )}
+
+
+
+
+
+
+
+ )}
+
+ );
+}
diff --git a/extensions/jaskiringg/confluence-ai/static/ui/src/api.js b/extensions/jaskiringg/confluence-ai/static/ui/src/api.js
new file mode 100644
index 0000000..2b32857
--- /dev/null
+++ b/extensions/jaskiringg/confluence-ai/static/ui/src/api.js
@@ -0,0 +1,5 @@
+import { invoke } from "@forge/bridge";
+
+export async function call(action, payload) {
+ return invoke(action, payload || {});
+}
diff --git a/extensions/jaskiringg/confluence-ai/static/ui/src/forgeBridgeStub.js b/extensions/jaskiringg/confluence-ai/static/ui/src/forgeBridgeStub.js
new file mode 100644
index 0000000..f496ab1
--- /dev/null
+++ b/extensions/jaskiringg/confluence-ai/static/ui/src/forgeBridgeStub.js
@@ -0,0 +1,21 @@
+export async function invoke(action, payload) {
+ const r = await fetch(`/api/${action}`, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify(payload || {}),
+ });
+ const data = await r.json().catch(() => ({}));
+ if (!r.ok) throw new Error(data.error || r.statusText);
+ return data;
+}
+
+export async function view() {
+ return {
+ getContext: async () => ({
+ extension: {
+ content: { id: localStorage.getItem("pageId") || "demo-page" },
+ space: { id: localStorage.getItem("spaceId") || "demo-space" },
+ },
+ }),
+ };
+}
diff --git a/extensions/jaskiringg/confluence-ai/static/ui/src/main.jsx b/extensions/jaskiringg/confluence-ai/static/ui/src/main.jsx
new file mode 100644
index 0000000..4ecd9cf
--- /dev/null
+++ b/extensions/jaskiringg/confluence-ai/static/ui/src/main.jsx
@@ -0,0 +1,10 @@
+import React from "react";
+import { createRoot } from "react-dom/client";
+import App from "./App.jsx";
+import "./styles.css";
+
+createRoot(document.getElementById("root")).render(
+
+
+
+);
diff --git a/extensions/jaskiringg/confluence-ai/static/ui/src/styles.css b/extensions/jaskiringg/confluence-ai/static/ui/src/styles.css
new file mode 100644
index 0000000..00a04e2
--- /dev/null
+++ b/extensions/jaskiringg/confluence-ai/static/ui/src/styles.css
@@ -0,0 +1,80 @@
+:root {
+ color-scheme: light;
+ --ink: #172b4d;
+ --muted: #626f86;
+ --line: #dcdfe4;
+ --bg: #f7f8f9;
+ --card: #fff;
+ --blue: #0c66e4;
+ --good: #dcfff1;
+ --bad: #ffeceb;
+ --warn: #fff7d6;
+ font-family: ui-sans-serif, "Atlassian Sans", system-ui, sans-serif;
+ color: var(--ink);
+ background: var(--bg);
+}
+
+* { box-sizing: border-box; }
+body { margin: 0; }
+.shell { max-width: 1100px; margin: 0 auto; padding: 20px 20px 48px; }
+.top { display: flex; justify-content: space-between; gap: 16px; align-items: flex-start; }
+.kicker { font-size: 12px; letter-spacing: .04em; text-transform: uppercase; color: var(--muted); }
+h1 { margin: 4px 0; font-size: 22px; }
+h2 { margin: 0 0 12px; font-size: 15px; }
+.meta, .hint { color: var(--muted); font-size: 13px; }
+.tabs, .row { display: flex; gap: 8px; }
+.row.wrap { flex-wrap: wrap; align-items: end; }
+.tabs button, button {
+ border: 1px solid var(--line);
+ background: var(--card);
+ color: var(--ink);
+ border-radius: 8px;
+ padding: 8px 12px;
+ cursor: pointer;
+ font: inherit;
+}
+button.on, .tabs button.on { border-color: var(--blue); color: var(--blue); background: #e9f2ff; }
+button.ghost { background: transparent; }
+button:disabled { opacity: .55; cursor: not-allowed; }
+.banner { margin: 12px 0; padding: 10px 12px; border-radius: 8px; background: #e9f2ff; font-size: 13px; }
+.banner.bad { background: var(--bad); }
+.banner.warn { background: var(--warn); }
+.grid { display: grid; grid-template-columns: 1fr 1.2fr; gap: 16px; margin-top: 16px; }
+.panel { background: var(--card); border: 1px solid var(--line); border-radius: 12px; padding: 16px; }
+.sections, .queue { list-style: none; padding: 0; margin: 0 0 12px; }
+.sections button, .queue button {
+ width: 100%;
+ text-align: left;
+ margin-bottom: 6px;
+}
+.lvl { display: block; }
+.l0 { padding-left: 0; }
+.l2 { padding-left: 10px; }
+.l3 { padding-left: 20px; }
+.sections em { color: var(--muted); font-size: 12px; }
+label { display: block; font-size: 13px; color: var(--muted); margin: 10px 0; }
+label.inline { margin: 0 12px 12px 0; }
+textarea, select {
+ display: block;
+ width: 100%;
+ margin-top: 6px;
+ border: 1px solid var(--line);
+ border-radius: 8px;
+ padding: 8px;
+ font: inherit;
+ color: var(--ink);
+ background: #fff;
+}
+.change { border: 1px solid var(--line); border-radius: 10px; padding: 10px; margin: 10px 0; }
+.change.ok { background: #f4fffa; }
+.change.no { background: #fff8f7; }
+.change header { display: flex; gap: 8px; flex-direction: column; margin-bottom: 8px; font-size: 13px; }
+.diff { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; font-size: 13px; }
+.old { background: var(--bad); padding: 8px; border-radius: 8px; }
+.new { background: var(--good); padding: 8px; border-radius: 8px; }
+.queue .current button { border-color: var(--blue); }
+
+@media (max-width: 800px) {
+ .grid, .diff { grid-template-columns: 1fr; }
+ .top { flex-direction: column; }
+}
diff --git a/extensions/jaskiringg/confluence-ai/static/ui/vite.config.js b/extensions/jaskiringg/confluence-ai/static/ui/vite.config.js
new file mode 100644
index 0000000..a4630b2
--- /dev/null
+++ b/extensions/jaskiringg/confluence-ai/static/ui/vite.config.js
@@ -0,0 +1,25 @@
+import path from "node:path";
+import { defineConfig } from "vite";
+import react from "@vitejs/plugin-react";
+
+const forge = process.env.FORGE === "1";
+
+export default defineConfig({
+ plugins: [react()],
+ base: "./",
+ resolve: forge
+ ? {}
+ : {
+ alias: {
+ "@forge/bridge": path.resolve(__dirname, "src/forgeBridgeStub.js"),
+ },
+ },
+ server: {
+ port: 5178,
+ proxy: { "/api": "http://127.0.0.1:8787" },
+ },
+ build: {
+ outDir: "build",
+ emptyOutDir: true,
+ },
+});
From 525c3bbd06b609a5226ef01b129eddf6fcc82662 Mon Sep 17 00:00:00 2001
From: Jaskirat Singh <280064190+jaskiringg@users.noreply.github.com>
Date: Sun, 9 Aug 2026 20:50:45 +0530
Subject: [PATCH 2/5] Make the Confluence Forge app edit live pages, not only
the sidecar demo.
Sideload now uses asApp + a real app id so section review and the space queue run on Confluence Cloud after install.
---
extensions/jaskiringg/confluence-ai/README.md | 10 +-
.../jaskiringg/confluence-ai/manifest.yml | 12 +-
.../jaskiringg/confluence-ai/src/handlers.js | 42 ++++-
.../jaskiringg/confluence-ai/src/index.js | 2 +-
.../confluence-ai/src/lib/confluence.js | 150 +++++++++++++++---
.../confluence-ai/static/ui/package-lock.json | 133 ++++++++++++++--
.../confluence-ai/static/ui/package.json | 1 +
.../confluence-ai/static/ui/src/App.jsx | 34 ++--
8 files changed, 320 insertions(+), 64 deletions(-)
diff --git a/extensions/jaskiringg/confluence-ai/README.md b/extensions/jaskiringg/confluence-ai/README.md
index e0f3008..e149f95 100644
--- a/extensions/jaskiringg/confluence-ai/README.md
+++ b/extensions/jaskiringg/confluence-ai/README.md
@@ -32,11 +32,13 @@ To hit a real Confluence Cloud site from the sidecar, set `CONFLUENCE_BASE_URL`,
1. Create a free Confluence Cloud site.
2. `npm i -g @forge/cli` then `forge login`.
-3. In this folder: `forge register` (replaces the placeholder `app.id` in `manifest.yml`).
-4. `forge variables set SUPERDOCS_API_KEY`
-5. `FORGE=1 npm --prefix static/ui run build && forge deploy && forge install`
+3. In this folder: `forge register` (writes a real `app.id` into `manifest.yml` — re-register if you fork).
+4. `forge variables set SUPERDOCS_API_KEY "$SUPERDOCS_API_KEY" --encrypt`
+5. `FORGE=1 npm --prefix static/ui run build && forge deploy && forge install --product Confluence --site YOUR-SITE.atlassian.net`
-Page action: **SuperDocs AI edit**. Space page: **SuperDocs space queue**.
+Page byline: **AI review**. Space page: **SuperDocs space queue**. Page menu → Apps → **SuperDocs AI edit**.
+
+This copy is sideloaded on `jaskiring.atlassian.net` (personal site, not public). Reviewers should run the sidecar or sideload their own site.
Marketplace listing is out of scope; sideload is enough.
diff --git a/extensions/jaskiringg/confluence-ai/manifest.yml b/extensions/jaskiringg/confluence-ai/manifest.yml
index b797e24..68a53c4 100644
--- a/extensions/jaskiringg/confluence-ai/manifest.yml
+++ b/extensions/jaskiringg/confluence-ai/manifest.yml
@@ -29,24 +29,22 @@ resources:
app:
runtime:
- name: nodejs20.x
+ name: nodejs22.x
memoryMB: 256
- id: ari:cloud:ecosystem::app/00000000-0000-4000-8000-000000000001
+ id: ari:cloud:ecosystem::app/7f17f3ef-6f7f-4c69-a213-590e8cacdfcc
permissions:
scopes:
- read:page:confluence
- write:page:confluence
- read:space:confluence
- - read:confluence-user
+ - read:user:confluence
- search:confluence
- - write:confluence-content
- - read:confluence-content.summary
+ - read:content.property:confluence
+ - write:content.property:confluence
external:
fetch:
backend:
- address: api.superdocs.app
- category: analytics
- inScopeEUD: false
client:
- address: api.superdocs.app
diff --git a/extensions/jaskiringg/confluence-ai/src/handlers.js b/extensions/jaskiringg/confluence-ai/src/handlers.js
index 03a1af4..c4a692b 100644
--- a/extensions/jaskiringg/confluence-ai/src/handlers.js
+++ b/extensions/jaskiringg/confluence-ai/src/handlers.js
@@ -35,19 +35,49 @@ function envFrom(ctx) {
CONFLUENCE_BASE_URL: process.env.CONFLUENCE_BASE_URL,
CONFLUENCE_EMAIL: process.env.CONFLUENCE_EMAIL,
CONFLUENCE_API_TOKEN: process.env.CONFLUENCE_API_TOKEN,
+ useForgeApi: ctx.kind === "forge",
};
}
+function isDemoId(id) {
+ return !id || String(id).startsWith("demo");
+}
+
+function resolvePageId(payload, ctx) {
+ if (!isDemoId(payload.pageId)) return String(payload.pageId);
+ if (!isDemoId(ctx.pageId)) return String(ctx.pageId);
+ return payload.pageId || ctx.pageId || DEMO_PAGE_ID;
+}
+
+function resolveSpaceId(payload, ctx) {
+ if (!isDemoId(payload.spaceId)) return String(payload.spaceId);
+ if (!isDemoId(ctx.spaceId)) return String(ctx.spaceId);
+ return payload.spaceId || ctx.spaceId || DEMO_SPACE_ID;
+}
+
function sessionKey(pageId) {
return `conf-${pageId}`;
}
async function getContext(payload, ctx) {
const env = envFrom(ctx);
- const pageId = payload.pageId || ctx.pageId || DEMO_PAGE_ID;
- const spaceId = payload.spaceId || ctx.spaceId || DEMO_SPACE_ID;
+ const pageId = resolvePageId(payload, ctx);
+ const spaceId = resolveSpaceId(payload, ctx);
+
+ if (env.useForgeApi && isDemoId(pageId) && !isDemoId(spaceId)) {
+ const pages = await listSpacePages(env, spaceId);
+ const first = pages[0];
+ if (first) return getContext({ ...payload, pageId: first.id, spaceId }, ctx);
+ return {
+ page: { id: null, title: "Space queue", version: 1, spaceId, demo: false },
+ sections: [],
+ review: { status: "draft" },
+ hasSuperDocsKey: !!env.SUPERDOCS_API_KEY,
+ };
+ }
+
const page = await getPage(env, pageId);
- const { sections, doc } = listSections(page.adf);
+ const { sections } = listSections(page.adf);
const review = await getReviewState(env, pageId, reviewMemory);
return {
page: {
@@ -65,7 +95,7 @@ async function getContext(payload, ctx) {
async function loadPageHtml(payload, ctx) {
const env = envFrom(ctx);
- const page = await getPage(env, payload.pageId);
+ const page = await getPage(env, resolvePageId(payload, ctx));
const { start, end } = payload;
if (start != null && end != null) {
const sliced = sliceSectionHtml(page.adf, start, end);
@@ -79,7 +109,7 @@ async function loadPageHtml(payload, ctx) {
async function startEdit(payload, ctx) {
const env = envFrom(ctx);
- const page = await getPage(env, payload.pageId);
+ const page = await getPage(env, resolvePageId(payload, ctx));
const review = await getReviewState(env, page.id, reviewMemory);
if (review.status === "awaiting_approver" && payload.role !== "approver") {
throw new Error("This page is waiting on the named approver. Publish is blocked.");
@@ -230,7 +260,7 @@ async function sendToApprover(payload, ctx) {
async function listPages(payload, ctx) {
const env = envFrom(ctx);
- const spaceId = payload.spaceId || DEMO_SPACE_ID;
+ const spaceId = resolveSpaceId(payload, ctx);
const pages = await listSpacePages(env, spaceId);
return { spaceId, pages };
}
diff --git a/extensions/jaskiringg/confluence-ai/src/index.js b/extensions/jaskiringg/confluence-ai/src/index.js
index f911ec8..cae9dd4 100644
--- a/extensions/jaskiringg/confluence-ai/src/index.js
+++ b/extensions/jaskiringg/confluence-ai/src/index.js
@@ -1,4 +1,4 @@
-const Resolver = require("@forge/resolver");
+const Resolver = require("@forge/resolver").default;
const { handlers } = require("./handlers");
const resolver = new Resolver();
diff --git a/extensions/jaskiringg/confluence-ai/src/lib/confluence.js b/extensions/jaskiringg/confluence-ai/src/lib/confluence.js
index 7804450..c4e1202 100644
--- a/extensions/jaskiringg/confluence-ai/src/lib/confluence.js
+++ b/extensions/jaskiringg/confluence-ai/src/lib/confluence.js
@@ -1,12 +1,98 @@
-const fs = require("fs");
-const path = require("path");
-
const DEMO_PAGE_ID = "demo-page";
const DEMO_SPACE_ID = "demo-space";
+const DEMO_ADF = {
+ type: "doc",
+ version: 1,
+ content: [
+ {
+ type: "heading",
+ attrs: { level: 1 },
+ content: [{ type: "text", text: "IT Change Policy — North Region" }],
+ },
+ {
+ type: "paragraph",
+ content: [
+ {
+ type: "text",
+ text: "All production changes require a CAB ticket before cutover. Emergency changes still need a follow-up ticket within 24 hours.",
+ },
+ ],
+ },
+ {
+ type: "heading",
+ attrs: { level: 2 },
+ content: [{ type: "text", text: "Freeze windows" }],
+ },
+ {
+ type: "paragraph",
+ content: [
+ {
+ type: "text",
+ text: "North region freeze is 02:00-06:00 IST on release nights. No master-data edits during freeze.",
+ },
+ ],
+ },
+ {
+ type: "table",
+ attrs: { isNumberColumnEnabled: false, layout: "default" },
+ content: [
+ {
+ type: "tableRow",
+ content: [
+ {
+ type: "tableHeader",
+ attrs: {},
+ content: [{ type: "paragraph", content: [{ type: "text", text: "System" }] }],
+ },
+ {
+ type: "tableHeader",
+ attrs: {},
+ content: [{ type: "paragraph", content: [{ type: "text", text: "Owner" }] }],
+ },
+ ],
+ },
+ {
+ type: "tableRow",
+ content: [
+ {
+ type: "tableCell",
+ attrs: {},
+ content: [{ type: "paragraph", content: [{ type: "text", text: "Order API" }] }],
+ },
+ {
+ type: "tableCell",
+ attrs: {},
+ content: [{ type: "paragraph", content: [{ type: "text", text: "Platform" }] }],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ type: "heading",
+ attrs: { level: 2 },
+ content: [{ type: "text", text: "Rollback" }],
+ },
+ {
+ type: "paragraph",
+ content: [
+ {
+ type: "text",
+ text: "If error rate exceeds 2% after cutover, roll back within 15 minutes and page the on-call.",
+ },
+ ],
+ },
+ {
+ type: "codeBlock",
+ attrs: { language: "bash" },
+ content: [{ type: "text", text: "kubectl rollout undo deploy/order-api" }],
+ },
+ ],
+};
+
function loadDemoAdf() {
- const p = path.join(__dirname, "../../fixtures/demo-page.adf.json");
- return JSON.parse(fs.readFileSync(p, "utf8"));
+ return JSON.parse(JSON.stringify(DEMO_ADF));
}
function authHeader(email, token) {
@@ -17,6 +103,29 @@ function confluenceConfigured(env) {
return !!(env.CONFLUENCE_BASE_URL && env.CONFLUENCE_EMAIL && env.CONFLUENCE_API_TOKEN);
}
+function liveConfluence(env, id) {
+ if (id && String(id).startsWith("demo")) return false;
+ return !!(env.useForgeApi || confluenceConfigured(env));
+}
+
+async function forgeFetch(pathname, { method = "GET", body } = {}) {
+ const { asApp, assumeTrustedRoute } = require("@forge/api");
+ const res = await asApp().requestConfluence(assumeTrustedRoute(pathname), {
+ method,
+ headers: {
+ Accept: "application/json",
+ ...(body ? { "Content-Type": "application/json" } : {}),
+ },
+ body: body ? JSON.stringify(body) : undefined,
+ });
+ const text = await res.text();
+ const data = text ? JSON.parse(text) : null;
+ if (!res.ok) {
+ throw new Error(`Confluence ${res.status}: ${text.slice(0, 500)}`);
+ }
+ return data;
+}
+
async function cfFetch(env, pathname, { method = "GET", body } = {}) {
const url = `${env.CONFLUENCE_BASE_URL.replace(/\/$/, "")}${pathname}`;
const res = await fetch(url, {
@@ -36,6 +145,11 @@ async function cfFetch(env, pathname, { method = "GET", body } = {}) {
return data;
}
+async function productFetch(env, pathname, opts) {
+ if (env.useForgeApi) return forgeFetch(pathname, opts);
+ return cfFetch(env, pathname, opts);
+}
+
function parseAdfFromPage(page) {
const value =
page?.body?.atlas_doc_format?.value ||
@@ -52,7 +166,7 @@ const DEMO_TITLES = {
};
async function getPage(env, pageId) {
- if (!confluenceConfigured(env) || String(pageId).startsWith("demo")) {
+ if (!liveConfluence(env, pageId)) {
const adf = loadDemoAdf();
if (pageId === "demo-page-2") {
adf.content[0] = {
@@ -78,7 +192,7 @@ async function getPage(env, pageId) {
demo: true,
};
}
- const page = await cfFetch(
+ const page = await productFetch(
env,
`/wiki/api/v2/pages/${encodeURIComponent(pageId)}?body-format=atlas_doc_format`
);
@@ -94,7 +208,7 @@ async function getPage(env, pageId) {
}
async function updatePage(env, { pageId, title, adf, versionNumber, message }) {
- if (!confluenceConfigured(env) || String(pageId).startsWith("demo")) {
+ if (!liveConfluence(env, pageId)) {
return {
id: DEMO_PAGE_ID,
title,
@@ -103,7 +217,7 @@ async function updatePage(env, { pageId, title, adf, versionNumber, message }) {
adf,
};
}
- return cfFetch(env, `/wiki/api/v2/pages/${encodeURIComponent(pageId)}`, {
+ return productFetch(env, `/wiki/api/v2/pages/${encodeURIComponent(pageId)}`, {
method: "PUT",
body: {
id: String(pageId),
@@ -122,14 +236,14 @@ async function updatePage(env, { pageId, title, adf, versionNumber, message }) {
}
async function listSpacePages(env, spaceId) {
- if (!confluenceConfigured(env) || spaceId === DEMO_SPACE_ID) {
+ if (!liveConfluence(env, spaceId) || spaceId === DEMO_SPACE_ID) {
return [
{ id: DEMO_PAGE_ID, title: "IT Change Policy — North Region", status: "current" },
{ id: "demo-page-2", title: "Go-live checklist (draft)", status: "current" },
{ id: "demo-page-3", title: "SIT notes — week 12", status: "current" },
];
}
- const data = await cfFetch(
+ const data = await productFetch(
env,
`/wiki/api/v2/spaces/${encodeURIComponent(spaceId)}/pages?limit=50&status=current`
);
@@ -141,13 +255,13 @@ async function listSpacePages(env, spaceId) {
}
async function searchUsers(env, query) {
- if (!confluenceConfigured(env)) {
+ if (!liveConfluence(env)) {
return [
{ accountId: "demo-approver", displayName: "Priya Sharma (demo)", email: "priya@example.com" },
{ accountId: "demo-author", displayName: "Jaskirat Singh (demo)", email: "singhjass6404@gmail.com" },
].filter((u) => !query || u.displayName.toLowerCase().includes(query.toLowerCase()));
}
- const data = await cfFetch(
+ const data = await productFetch(
env,
`/wiki/rest/api/search/user?cql=${encodeURIComponent(`user.fullname~"${query}"`)}&limit=10`
);
@@ -162,11 +276,11 @@ const PROPERTY_KEY = "superdocs-review";
async function getReviewState(env, pageId, memory) {
if (memory.has(pageId)) return memory.get(pageId);
- if (!confluenceConfigured(env) || pageId === DEMO_PAGE_ID) {
+ if (!liveConfluence(env, pageId) || pageId === DEMO_PAGE_ID) {
return { status: "draft" };
}
try {
- const data = await cfFetch(
+ const data = await productFetch(
env,
`/wiki/rest/api/content/${encodeURIComponent(pageId)}/property/${PROPERTY_KEY}`
);
@@ -178,15 +292,15 @@ async function getReviewState(env, pageId, memory) {
async function setReviewState(env, pageId, value, memory) {
memory.set(pageId, value);
- if (!confluenceConfigured(env) || pageId === DEMO_PAGE_ID) return value;
+ if (!liveConfluence(env, pageId) || pageId === DEMO_PAGE_ID) return value;
try {
- await cfFetch(
+ await productFetch(
env,
`/wiki/rest/api/content/${encodeURIComponent(pageId)}/property/${PROPERTY_KEY}`,
{ method: "PUT", body: { key: PROPERTY_KEY, value } }
);
} catch {
- await cfFetch(
+ await productFetch(
env,
`/wiki/rest/api/content/${encodeURIComponent(pageId)}/property`,
{ method: "POST", body: { key: PROPERTY_KEY, value } }
diff --git a/extensions/jaskiringg/confluence-ai/static/ui/package-lock.json b/extensions/jaskiringg/confluence-ai/static/ui/package-lock.json
index afa45c4..5dc4f09 100644
--- a/extensions/jaskiringg/confluence-ai/static/ui/package-lock.json
+++ b/extensions/jaskiringg/confluence-ai/static/ui/package-lock.json
@@ -8,6 +8,7 @@
"name": "confluence-ai-ui",
"version": "0.1.0",
"dependencies": {
+ "@forge/bridge": "^4.5.3",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
@@ -16,11 +17,51 @@
"vite": "^6.0.7"
}
},
+ "node_modules/@atlaskit/ds-lib": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/@atlaskit/ds-lib/-/ds-lib-2.7.0.tgz",
+ "integrity": "sha512-+U4aPE2dBRFUBYWCM9vkrwJGrLAYVcK30ZpEiQDNpM2D7K41223TfEijC8azaN4VM3tsCgwSKBWwniloNxqYbA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@atlaskit/platform-feature-flags": "^0.3.0",
+ "@babel/runtime": "^7.0.0",
+ "bind-event-listener": "^3.0.0",
+ "react-uid": "^2.2.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/@atlaskit/platform-feature-flags": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/@atlaskit/platform-feature-flags/-/platform-feature-flags-0.3.0.tgz",
+ "integrity": "sha512-/0u5fFJ0Rw2j4M5wzsXgaHO6Ey12oekPCDTRvmmAIp4GO9T2Swbl80bavLAPSOmSHMhHTSuvRxiJveZXfQ21IQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@babel/runtime": "^7.0.0"
+ }
+ },
+ "node_modules/@atlaskit/tokens": {
+ "version": "1.61.0",
+ "resolved": "https://registry.npmjs.org/@atlaskit/tokens/-/tokens-1.61.0.tgz",
+ "integrity": "sha512-gRkBDZOaQffJHg9g+hYFgPjQ0Hz4XIDaK5WEttIGyhi2USsGsvDvUeED8liqcQNwssH/5UFxIFp3FmEwo0DoFA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@atlaskit/ds-lib": "^2.6.0",
+ "@atlaskit/platform-feature-flags": "^0.3.0",
+ "@babel/runtime": "^7.0.0",
+ "@babel/traverse": "^7.23.2",
+ "@babel/types": "^7.20.0",
+ "bind-event-listener": "^3.0.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
"node_modules/@babel/code-frame": {
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
"integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-validator-identifier": "^7.29.7",
@@ -76,7 +117,6 @@
"version": "7.29.8",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz",
"integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.29.8",
@@ -110,7 +150,6 @@
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz",
"integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -162,7 +201,6 @@
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
"integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -172,7 +210,6 @@
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
"integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -206,7 +243,6 @@
"version": "7.29.8",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz",
"integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.29.8"
@@ -250,11 +286,19 @@
"@babel/core": "^7.0.0-0"
}
},
+ "node_modules/@babel/runtime": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz",
+ "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/@babel/template": {
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz",
"integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.29.7",
@@ -269,7 +313,6 @@
"version": "7.29.8",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz",
"integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.29.7",
@@ -288,7 +331,6 @@
"version": "7.29.8",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz",
"integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.29.7",
@@ -740,11 +782,30 @@
"node": ">=18"
}
},
+ "node_modules/@forge/bridge": {
+ "version": "4.5.3",
+ "resolved": "https://registry.npmjs.org/@forge/bridge/-/bridge-4.5.3.tgz",
+ "integrity": "sha512-vc0tcKphj4bWCTDT6T8Jvi5UHzy9AvUzCP4dGre7rXxWZa2n8AnNPLCbvJwddjCLNm1oGLe3jq3FMrDFhkl5kw==",
+ "license": "SEE LICENSE IN LICENSE.txt",
+ "dependencies": {
+ "@atlaskit/tokens": "^1.58.0",
+ "@forge/i18n": "0.0.6",
+ "@types/history": "^4.7.11"
+ }
+ },
+ "node_modules/@forge/i18n": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/@forge/i18n/-/i18n-0.0.6.tgz",
+ "integrity": "sha512-iIK0SxWwMUHp8ThYiBU+Cq/ty1Ng1dGEr+apWbh6SwbxaXWR8MGRr91AFj3BLLAiC6FXYprIBclgI8tRf0DxmA==",
+ "license": "SEE LICENSE IN LICENSE.txt",
+ "dependencies": {
+ "lodash": "^4.17.21"
+ }
+ },
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.0",
@@ -766,7 +827,6 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
@@ -776,14 +836,12 @@
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
- "dev": true,
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.31",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
@@ -1258,6 +1316,12 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/history": {
+ "version": "4.7.11",
+ "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz",
+ "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==",
+ "license": "MIT"
+ },
"node_modules/@vitejs/plugin-react": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
@@ -1292,6 +1356,12 @@
"node": ">=6.0.0"
}
},
+ "node_modules/bind-event-listener": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/bind-event-listener/-/bind-event-listener-3.0.0.tgz",
+ "integrity": "sha512-PJvH288AWQhKs2v9zyfYdPzlPqf5bXbGMmhmUIY9x4dAUGIWgomO771oBQNwJnMQSnUIXhKu6sgzpBRXTlvb8Q==",
+ "license": "MIT"
+ },
"node_modules/browserslist": {
"version": "4.28.8",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz",
@@ -1358,7 +1428,6 @@
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
@@ -1484,7 +1553,6 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
- "dev": true,
"license": "MIT",
"bin": {
"jsesc": "bin/jsesc"
@@ -1506,6 +1574,12 @@
"node": ">=6"
}
},
+ "node_modules/lodash": {
+ "version": "4.18.1",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
+ "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
+ "license": "MIT"
+ },
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@@ -1532,7 +1606,6 @@
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "dev": true,
"license": "MIT"
},
"node_modules/nanoid": {
@@ -1568,7 +1641,6 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
- "dev": true,
"license": "ISC"
},
"node_modules/picomatch": {
@@ -1648,6 +1720,27 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react-uid": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/react-uid/-/react-uid-2.4.0.tgz",
+ "integrity": "sha512-+MVs/25NrcZuGrmlVRWPOSsbS8y72GJOBsR7d68j3/wqOrRBF52U29XAw4+XSelw0Vm6s5VmGH5mCbTCPGVCVg==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/rollup": {
"version": "4.62.4",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.4.tgz",
@@ -1740,6 +1833,12 @@
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "license": "0BSD"
+ },
"node_modules/update-browserslist-db": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.0.tgz",
diff --git a/extensions/jaskiringg/confluence-ai/static/ui/package.json b/extensions/jaskiringg/confluence-ai/static/ui/package.json
index defea1a..0752c6d 100644
--- a/extensions/jaskiringg/confluence-ai/static/ui/package.json
+++ b/extensions/jaskiringg/confluence-ai/static/ui/package.json
@@ -8,6 +8,7 @@
"build": "vite build"
},
"dependencies": {
+ "@forge/bridge": "^4.5.3",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
diff --git a/extensions/jaskiringg/confluence-ai/static/ui/src/App.jsx b/extensions/jaskiringg/confluence-ai/static/ui/src/App.jsx
index 8afb9e5..4839ef4 100644
--- a/extensions/jaskiringg/confluence-ai/static/ui/src/App.jsx
+++ b/extensions/jaskiringg/confluence-ai/static/ui/src/App.jsx
@@ -24,20 +24,28 @@ export default function App() {
const pageId = ctx?.page?.id || "demo-page";
const spaceId = ctx?.page?.spaceId || "demo-space";
- async function refresh() {
- const data = await call("getContext", { pageId, spaceId });
+ async function refresh(nextPageId) {
+ const data = await call("getContext", {
+ pageId: nextPageId || (ctx?.page?.id && !String(ctx.page.id).startsWith("demo") ? ctx.page.id : undefined),
+ spaceId: ctx?.page?.spaceId && !String(ctx.page.spaceId).startsWith("demo") ? ctx.page.spaceId : undefined,
+ });
setCtx(data);
if (!section && data.sections?.[0]) setSection(data.sections[0]);
+ return data;
}
useEffect(() => {
- refresh().catch((e) => setErr(e.message));
- call("listPages", { spaceId: "demo-space" })
- .then((d) => setQueue(d.pages || []))
- .catch(() => {});
- call("findUsers", { query: "" })
- .then((d) => setUsers(d.users || []))
- .catch(() => {});
+ (async () => {
+ try {
+ const data = await refresh();
+ const listed = await call("listPages", { spaceId: data.page?.spaceId });
+ setQueue(listed.pages || []);
+ const found = await call("findUsers", { query: "" });
+ setUsers(found.users || []);
+ } catch (e) {
+ setErr(e.message);
+ }
+ })();
}, []);
async function pollUntilSettled(jobId, sessionId) {
@@ -209,8 +217,12 @@ export default function App() {