diff --git a/README.md b/README.md index 6ceb56c..39a8cc0 100644 --- a/README.md +++ b/README.md @@ -22,21 +22,29 @@ ## See it working first -Before setup, this sample n8n configuration shows GPT-5.6 model aliases in the -OpenAI Chat Model selector. The exact model list depends on the signed-in -ChatGPT account and can change over time. +This documented example follows a tested n8n OpenAI credential through a +published Telegram-triggered workflow. It is a product-operation record, not +an endorsement, sponsorship, or affiliation by OpenAI, n8n, Hostinger, +Telegram, or AppBuildersPH.
- Model selector listing GPT-5.6 Sol, Luna, and Terra model aliases -
Compatibility preview: the model selector includes gpt-5.6-sol, gpt-5.6-luna, and gpt-5.6-terra.
+ n8n OpenAI credential dialog showing that the connection test succeeded +
The n8n OpenAI credential connection test succeeded; the credential value itself is obscured.
-After Relmio completes the installation, Docker shows the existing n8n stack -and the new private OAuth sidecar running together. +
+ n8n model selector with gpt-5.6-terra selected and an account-specific model list +
This signed-in account's n8n model list includes gpt-5.6-terra; model availability is account-dependent and can change.
+
- Docker Desktop showing the n8n and n8n-openai-oauth containers running -
Completed setup: the existing n8n stack and the n8n-openai-oauth sidecar are both running.
+ Successful n8n execution of a Telegram-triggered workflow with HTTP Request, Basic LLM Chain, and AI Agent branches +
The published Telegram-triggered n8n workflow completed successfully across its HTTP Request, Basic LLM Chain, and AI Agent branches.
+
+ +
+ Telegram conversation receiving HTTP Request, Basic LLM Chain, and AI Agent outputs +
Telegram received the HTTP Request, Basic LLM Chain, and AI Agent outputs sent by that workflow.
## Quick install @@ -380,7 +388,13 @@ you want to inspect, reproduce, improve, or debug the method, use Do **not** run an installer command on the VPS. Run it on the computer where you will complete the browser sign-in. -### 1. Start the newest published wizard +### 1. Choose an installation method in the hosted wizard + +The hosted installation page starts with a selector for macOS/Linux, +Homebrew, PowerShell, Command Prompt, and NPX. Choose the terminal already +installed on your computer, then run the displayed command locally. + +![Step 1: hosted installation method selector](docs/images/setup/00-install-methods.png) Choose a terminal already installed on your computer. @@ -452,9 +466,10 @@ npm view relmio version ``` > [!NOTE] -> Every wizard screenshot below comes from the built-in sanitized preview. It -> uses a reserved documentation IP, fake fingerprint, fake container data, and -> fake models. No real VPS, credential, or browser session is shown. +> These current product screenshots omit or redact sensitive values. In +> particular, the VPS identity screen redacts the address and SSH fingerprint; +> none of the screenshots includes an OAuth token, password, private key, or +> live wizard session URL. ### 2. Complete or reuse the local ChatGPT sign-in @@ -469,11 +484,10 @@ credential already exists, **Continue to VPS** reuses it. Use **Refresh ChatGPT sign-in** when it is expired, belongs to another account, or you want a new session. -After a fresh login, check the **Credential updated** timestamp. It is shown -in your computer's local time so you can tell that the browser approval -actually reached the wizard. +After a fresh login, the browser confirmation states that the ChatGPT +credentials were saved locally; return to the terminal to continue. -![Step 1: local ChatGPT credential found with its update timestamp](docs/images/setup/01-local-sign-in-ready.png) +![Step 2: local ChatGPT sign-in completion confirmation](docs/images/setup/01-local-sign-in-ready.png) If a browser extension named **Sign in with ChatGPT** or **OpenAI OAuth** captures the callback, temporarily disable that extension and start the @@ -486,11 +500,11 @@ Enter the VPS address exactly as your provider shows it. Select **Check server identity**, compare the SHA-256 fingerprint with the intended server, and confirm it before the password field unlocks. -![Step 2: sanitized VPS address and SSH fingerprint confirmation](docs/images/setup/02-vps-identity-confirmed.png) +![Step 3: VPS identity confirmation with the address and SSH fingerprint redacted](docs/images/setup/02-vps-identity-confirmed.png) -The screenshot uses the reserved documentation address `192.0.2.10`, a fake -fingerprint, and an empty password field. Never publish a real password, -private key, session URL, or OAuth file. +The screenshot redacts the VPS address and SSH fingerprint and obscures the +password field. Never publish a real password, private key, session URL, or +OAuth file. ### 4. Choose the detected n8n container and shared network @@ -498,7 +512,7 @@ The wizard connects over SSH, runs read-only Docker discovery, and lists the running n8n container and its networks. Choose the network that n8n should share with the sidecar; on many Hostinger templates it is named `proxy`. -![Step 3: sanitized n8n container and Docker network discovery](docs/images/setup/03-n8n-detected.png) +![Step 4: detected n8n container and shared Docker network](docs/images/setup/03-n8n-detected.png) ### 5. Review and approve the exact plan @@ -507,7 +521,7 @@ managed directory, service, Docker network, and private hostname. It also states the forbidden actions: no n8n edit, rebuild, restart, or recreation; no host port; and no Traefik route. -![Step 4: exact sidecar-only installation plan](docs/images/setup/04-install-plan.png) +![Step 5: exact sidecar-only installation plan](docs/images/setup/04-install-plan.png) Only after you select the approval checkbox can the wizard upload the OAuth file and build the separate sidecar. @@ -517,7 +531,7 @@ file and build the separate sidecar. The final screen appears only after the sidecar is healthy, the model list is reachable, and Docker reports no published host port. -![Step 5: verified private bridge and n8n credential values](docs/images/setup/05-bridge-ready.png) +![Step 6: verified private bridge and n8n credential values](docs/images/setup/05-bridge-ready.png) Use the button beside each value to copy it individually, or select **Copy credential settings** for the labeled credential set. Then create or diff --git a/docs/images/examples/gpt-56-model-selector.png b/docs/images/examples/gpt-56-model-selector.png index 9ce452c..a9f5f1b 100644 Binary files a/docs/images/examples/gpt-56-model-selector.png and b/docs/images/examples/gpt-56-model-selector.png differ diff --git a/docs/images/examples/n8n-openai-credential-connected.png b/docs/images/examples/n8n-openai-credential-connected.png new file mode 100644 index 0000000..36a541e Binary files /dev/null and b/docs/images/examples/n8n-openai-credential-connected.png differ diff --git a/docs/images/examples/telegram-model-results.png b/docs/images/examples/telegram-model-results.png new file mode 100644 index 0000000..8337c1f Binary files /dev/null and b/docs/images/examples/telegram-model-results.png differ diff --git a/docs/images/examples/telegram-n8n-workflow-execution.png b/docs/images/examples/telegram-n8n-workflow-execution.png new file mode 100644 index 0000000..e249a80 Binary files /dev/null and b/docs/images/examples/telegram-n8n-workflow-execution.png differ diff --git a/docs/images/setup/00-install-methods.png b/docs/images/setup/00-install-methods.png new file mode 100644 index 0000000..a6773c4 Binary files /dev/null and b/docs/images/setup/00-install-methods.png differ diff --git a/docs/images/setup/01-local-sign-in-ready.png b/docs/images/setup/01-local-sign-in-ready.png index ab51e8d..2a9b145 100644 Binary files a/docs/images/setup/01-local-sign-in-ready.png and b/docs/images/setup/01-local-sign-in-ready.png differ diff --git a/docs/images/setup/02-vps-identity-confirmed.png b/docs/images/setup/02-vps-identity-confirmed.png index 7673fbb..19d1147 100644 Binary files a/docs/images/setup/02-vps-identity-confirmed.png and b/docs/images/setup/02-vps-identity-confirmed.png differ diff --git a/docs/images/setup/03-n8n-detected.png b/docs/images/setup/03-n8n-detected.png index ee02fc2..90c4897 100644 Binary files a/docs/images/setup/03-n8n-detected.png and b/docs/images/setup/03-n8n-detected.png differ diff --git a/docs/images/setup/04-install-plan.png b/docs/images/setup/04-install-plan.png index e285754..101bae9 100644 Binary files a/docs/images/setup/04-install-plan.png and b/docs/images/setup/04-install-plan.png differ diff --git a/docs/images/setup/05-bridge-ready.png b/docs/images/setup/05-bridge-ready.png index 3367ec9..e44932e 100644 Binary files a/docs/images/setup/05-bridge-ready.png and b/docs/images/setup/05-bridge-ready.png differ diff --git a/npm/README.md b/npm/README.md index 1acba05..33d8911 100644 --- a/npm/README.md +++ b/npm/README.md @@ -15,21 +15,29 @@ ## See it working first -Before setup, this sample n8n configuration shows GPT-5.6 model aliases in the -OpenAI Chat Model selector. The exact model list depends on the signed-in -ChatGPT account and can change over time. +This documented example follows a tested n8n OpenAI credential through a +published Telegram-triggered workflow. It is a product-operation record, not +an endorsement, sponsorship, or affiliation by OpenAI, n8n, Hostinger, +Telegram, or AppBuildersPH.
- Model selector listing GPT-5.6 Sol, Luna, and Terra model aliases -
Compatibility preview: the model selector includes gpt-5.6-sol, gpt-5.6-luna, and gpt-5.6-terra.
+ n8n OpenAI credential dialog showing that the connection test succeeded +
The n8n OpenAI credential connection test succeeded; the credential value itself is obscured.
-After Relmio completes the installation, Docker shows the existing n8n stack -and the new private OAuth sidecar running together. +
+ n8n model selector with gpt-5.6-terra selected and an account-specific model list +
This signed-in account's n8n model list includes gpt-5.6-terra; model availability is account-dependent and can change.
+
+ +
+ Successful n8n execution of a Telegram-triggered workflow with HTTP Request, Basic LLM Chain, and AI Agent branches +
The published Telegram-triggered n8n workflow completed successfully across its HTTP Request, Basic LLM Chain, and AI Agent branches.
+
- Docker Desktop showing the n8n and n8n-openai-oauth containers running -
Completed setup: the existing n8n stack and the n8n-openai-oauth sidecar are both running.
+ Telegram conversation receiving HTTP Request, Basic LLM Chain, and AI Agent outputs +
Telegram received the HTTP Request, Basic LLM Chain, and AI Agent outputs sent by that workflow.
Relmio is a local browser wizard that installs a private @@ -184,28 +192,47 @@ it is not currently advertised as supported. ## Visual walkthrough -All images below are sanitized previews with reserved addresses, fake server -data, and no real credential or session information. +These current product screenshots omit or redact sensitive values. The VPS +identity screen redacts the address and SSH fingerprint; none of the images +includes an OAuth token, password, private key, or live wizard session URL. + +### 1. Choose a hosted installation method + +Hosted Relmio installation page with macOS/Linux, Homebrew, PowerShell, Command Prompt, and NPX choices + +The hosted page starts with the installation-method selector. Run the selected +command locally, not on the VPS. + +### 2. Complete the local ChatGPT/Codex sign-in + +OpenAI OAuth browser page confirming that local ChatGPT credentials were saved + +The browser confirmation states that the ChatGPT credentials were saved locally. + +### 3. Verify the VPS identity -### 1. Confirm the local ChatGPT/Codex sign-in +VPS identity confirmation with the address and SSH fingerprint redacted -Sanitized local sign-in ready screen +Confirm the SSH host fingerprint before authentication. The screenshot redacts +the address and fingerprint and obscures the password field. -### 2. Verify the VPS identity +### 4. Choose the detected n8n container and network -Sanitized VPS fingerprint confirmation screen +Detected n8n container and shared Docker network -### 3. Choose the detected n8n container and network +Discovery is read-only; choose the existing n8n container and shared Docker +network. -Sanitized n8n discovery screen +### 5. Review the exact sidecar-only plan -### 4. Review the exact sidecar-only plan +Exact sidecar-only installation plan, including forbidden actions -Sanitized installation plan screen +The plan names the allowed sidecar work and explicitly excludes edits or +restarts of n8n, a published host port, and a Traefik route. -### 5. Copy the verified n8n settings +### 6. Copy the verified n8n settings -Sanitized verified bridge screen +Private bridge ready screen with n8n OpenAI credential settings Use these values in an n8n OpenAI credential: @@ -265,7 +292,8 @@ has the importable cURL version. - Relmio never edits, rebuilds, recreates, stops, or restarts the existing n8n container. - This is an unofficial community project, not affiliated with or endorsed by - OpenAI, xAI, or n8n. Provider access, models, limits, and policies can change. + OpenAI, xAI, n8n, Hostinger, Telegram, or AppBuildersPH. Provider access, + models, limits, and policies can change. - Use it only where your account, subscription, provider terms, and applicable policies allow. diff --git a/test/package-contents.test.js b/test/package-contents.test.js index 94a8707..86efde8 100644 --- a/test/package-contents.test.js +++ b/test/package-contents.test.js @@ -33,7 +33,11 @@ const expectedPackedFiles = new Set([ "docs/images/examples/gpt-56-model-selector.png", "docs/images/examples/gpt-56-sol-chat-model-run.png", "docs/images/examples/hosted-chat-connected.png", + "docs/images/examples/n8n-openai-credential-connected.png", "docs/images/examples/sidecar-docker-containers-running.png", + "docs/images/examples/telegram-model-results.png", + "docs/images/examples/telegram-n8n-workflow-execution.png", + "docs/images/setup/00-install-methods.png", "docs/images/setup/01-local-sign-in-ready.png", "docs/images/setup/02-vps-identity-confirmed.png", "docs/images/setup/03-n8n-detected.png", @@ -244,6 +248,22 @@ test("npm package substitutes a registry-safe README without changing GitHub dia npmReadme, /https:\/\/cdn\.jsdelivr\.net\/npm\/relmio@latest\/docs\/images\/setup\/05-bridge-ready\.png/u, ); + assert.match( + npmReadme, + /https:\/\/cdn\.jsdelivr\.net\/npm\/relmio@latest\/docs\/images\/setup\/00-install-methods\.png/u, + ); + assert.match( + npmReadme, + /https:\/\/cdn\.jsdelivr\.net\/npm\/relmio@latest\/docs\/images\/examples\/n8n-openai-credential-connected\.png/u, + ); + assert.match( + npmReadme, + /https:\/\/cdn\.jsdelivr\.net\/npm\/relmio@latest\/docs\/images\/examples\/telegram-model-results\.png/u, + ); + assert.match( + npmReadme, + /https:\/\/cdn\.jsdelivr\.net\/npm\/relmio@latest\/docs\/images\/examples\/telegram-n8n-workflow-execution\.png/u, + ); assert.match( npmReadme, /https:\/\/cdn\.jsdelivr\.net\/npm\/relmio@latest\/docs\/images\/examples\/hosted-chat-connected\.png/u, diff --git a/test/project.test.js b/test/project.test.js index 98212fb..97349d2 100644 --- a/test/project.test.js +++ b/test/project.test.js @@ -2,6 +2,46 @@ import assert from "node:assert/strict"; import { access, readFile } from "node:fs/promises"; import test from "node:test"; +const pngSignature = [137, 80, 78, 71, 13, 10, 26, 10]; +const maximumReadmeImageDimension = 10_000; + +function assertMetadataFreeReadmePng(image, name) { + assert.ok(image.length >= 33, `${name} is too short to be a PNG`); + assert.deepEqual([...image.subarray(0, 8)], pngSignature, `${name} signature`); + assert.equal(image.readUInt32BE(8), 13, `${name} IHDR length`); + assert.equal(image.toString("ascii", 12, 16), "IHDR", `${name} IHDR type`); + + const width = image.readUInt32BE(16); + const height = image.readUInt32BE(20); + assert.ok( + width >= 1 && width <= maximumReadmeImageDimension, + `${name} has an unreasonable width: ${width}`, + ); + assert.ok( + height >= 1 && height <= maximumReadmeImageDimension, + `${name} has an unreasonable height: ${height}`, + ); + + const chunkTypes = []; + let offset = 8; + while (offset < image.length) { + assert.ok(offset + 12 <= image.length, `${name} has a truncated PNG chunk`); + const length = image.readUInt32BE(offset); + const chunkEnd = offset + 12 + length; + assert.ok(chunkEnd <= image.length, `${name} has an oversized PNG chunk`); + chunkTypes.push(image.toString("ascii", offset + 4, offset + 8)); + offset = chunkEnd; + } + + assert.equal(offset, image.length, `${name} PNG length`); + assert.equal(chunkTypes[0], "IHDR", `${name} first PNG chunk`); + assert.equal(chunkTypes.at(-1), "IEND", `${name} final PNG chunk`); + assert.ok( + chunkTypes.every((type) => ["IHDR", "IDAT", "IEND"].includes(type)), + `${name} has unexpected PNG metadata chunks: ${chunkTypes.join(", ")}`, + ); +} + test("project pins the reviewed SSH dependency and Node runtime", async () => { const [packageContents, license, notice] = await Promise.all([ readFile("package.json", "utf8"), @@ -168,7 +208,7 @@ test("package-manager workflow builds review artifacts before release publicatio } }); -test("public README documents the latest npm walkthrough and sanitized images", async () => { +test("public README documents the latest npm walkthrough and screenshot records", async () => { const readme = await readFile("README.md", "utf8"); assert.match( @@ -179,8 +219,21 @@ test("public README documents the latest npm walkthrough and sanitized images", readme, /npx --yes --ignore-scripts relmio@latest/u, ); - assert.match(readme, /docs\/images\/setup\/01-local-sign-in-ready\.png/u); - assert.match(readme, /docs\/images\/setup\/05-bridge-ready\.png/u); + const requiredReadmeImages = [ + "docs/images/setup/00-install-methods.png", + "docs/images/setup/01-local-sign-in-ready.png", + "docs/images/setup/02-vps-identity-confirmed.png", + "docs/images/setup/03-n8n-detected.png", + "docs/images/setup/04-install-plan.png", + "docs/images/setup/05-bridge-ready.png", + "docs/images/examples/n8n-openai-credential-connected.png", + "docs/images/examples/gpt-56-model-selector.png", + "docs/images/examples/telegram-n8n-workflow-execution.png", + "docs/images/examples/telegram-model-results.png", + ]; + for (const imagePath of requiredReadmeImages) { + assert.ok(readme.includes(imagePath), `README references ${imagePath}`); + } assert.match(readme, /```mermaid/u); assert.match(readme, /Copy credential settings/u); assert.match(readme, /\[Changelog\]\(CHANGELOG\.md\)/u); @@ -204,81 +257,30 @@ test("public README documents the latest npm walkthrough and sanitized images", assert.match(readme, /## Legal/u); }); -test("README walkthrough images are metadata-free PNG files", async () => { - const images = await Promise.all( - [ - "01-local-sign-in-ready.png", - "02-vps-identity-confirmed.png", - "03-n8n-detected.png", - "04-install-plan.png", - "05-bridge-ready.png", - ].map((name) => readFile(`docs/images/setup/${name}`)), - ); - - for (const image of images) { - assert.deepEqual( - [...image.subarray(0, 8)], - [137, 80, 78, 71, 13, 10, 26, 10], - ); - assert.equal(image.readUInt32BE(16), 1440); - assert.ok(image.readUInt32BE(20) >= 1_000); - - const chunkTypes = []; - let offset = 8; - while (offset < image.length) { - const length = image.readUInt32BE(offset); - const type = image.toString("ascii", offset + 4, offset + 8); - chunkTypes.push(type); - offset += 12 + length; - } - assert.equal(offset, image.length); - assert.equal(chunkTypes[0], "IHDR"); - assert.equal(chunkTypes.at(-1), "IEND"); - assert.ok( - chunkTypes.every((type) => ["IHDR", "IDAT", "IEND"].includes(type)), - `unexpected PNG metadata chunk: ${chunkTypes.join(", ")}`, - ); - } -}); - -test("README example images are metadata-free PNG files", async () => { - const names = [ - "gpt-56-ai-agent-luna-run.png", - "gpt-56-ai-agent-sol-run.png", - "gpt-56-ai-agent-workflow.png", - "gpt-56-luna-chat-model-run.png", - "gpt-56-model-selector.png", - "gpt-56-sol-chat-model-run.png", - "hosted-chat-connected.png", - "sidecar-docker-containers-running.png", +test("README walkthrough and proof images are metadata-free PNG files", async () => { + const paths = [ + "docs/images/setup/00-install-methods.png", + "docs/images/setup/01-local-sign-in-ready.png", + "docs/images/setup/02-vps-identity-confirmed.png", + "docs/images/setup/03-n8n-detected.png", + "docs/images/setup/04-install-plan.png", + "docs/images/setup/05-bridge-ready.png", + "docs/images/examples/gpt-56-ai-agent-luna-run.png", + "docs/images/examples/gpt-56-ai-agent-sol-run.png", + "docs/images/examples/gpt-56-ai-agent-workflow.png", + "docs/images/examples/gpt-56-luna-chat-model-run.png", + "docs/images/examples/gpt-56-sol-chat-model-run.png", + "docs/images/examples/hosted-chat-connected.png", + "docs/images/examples/sidecar-docker-containers-running.png", + "docs/images/examples/telegram-model-results.png", + "docs/images/examples/telegram-n8n-workflow-execution.png", + "docs/images/examples/gpt-56-model-selector.png", + "docs/images/examples/n8n-openai-credential-connected.png", ]; - const images = await Promise.all( - names.map((name) => readFile(`docs/images/examples/${name}`)), - ); + const images = await Promise.all(paths.map((path) => readFile(path))); - for (const image of images) { - assert.deepEqual( - [...image.subarray(0, 8)], - [137, 80, 78, 71, 13, 10, 26, 10], - ); - assert.ok(image.readUInt32BE(16) >= 1); - assert.ok(image.readUInt32BE(20) >= 1); - - const chunkTypes = []; - let offset = 8; - while (offset < image.length) { - const length = image.readUInt32BE(offset); - const type = image.toString("ascii", offset + 4, offset + 8); - chunkTypes.push(type); - offset += 12 + length; - } - assert.equal(offset, image.length); - assert.equal(chunkTypes[0], "IHDR"); - assert.equal(chunkTypes.at(-1), "IEND"); - assert.ok( - chunkTypes.every((type) => ["IHDR", "IDAT", "IEND"].includes(type)), - `unexpected PNG metadata chunk: ${chunkTypes.join(", ")}`, - ); + for (const [index, image] of images.entries()) { + assertMetadataFreeReadmePng(image, paths[index]); } });