From 3f60129fd218fc9e6e0b9e4a8b5c7c99fd664286 Mon Sep 17 00:00:00 2001 From: anandpant Date: Mon, 2 Mar 2026 02:35:09 -0600 Subject: [PATCH 1/3] docs: add explicit opencode auth login step --- apps/web/src/app/opencode/page.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/web/src/app/opencode/page.tsx b/apps/web/src/app/opencode/page.tsx index 1291295..fa0708e 100644 --- a/apps/web/src/app/opencode/page.tsx +++ b/apps/web/src/app/opencode/page.tsx @@ -9,6 +9,7 @@ import { opencodePluginVersion } from "@/lib/opencode-version"; const npmUrl = "https://www.npmjs.com/package/@sketchi-app/opencode-excalidraw"; const githubUrl = "https://github.com/anand-testcompare/sketchi/tree/main/packages/opencode-excalidraw"; +const authCommand = "opencode auth login sketchi"; const webCommand = "opencode web"; const cliCommand = "opencode"; @@ -284,11 +285,14 @@ export default function OpenCodeDocsPage() { .
  • - Run opencode web. + Run {authCommand} once (device-flow sign-in).
  • - Then switch to opencode and run again in terminal - mode. + Run {webCommand}. +
  • +
  • + Then switch to {cliCommand} and run again in + terminal mode.
  • From c51eda28eafb3c9a3b3c1ee620fa1fd96fab5497 Mon Sep 17 00:00:00 2001 From: anandpant Date: Mon, 2 Mar 2026 02:43:48 -0600 Subject: [PATCH 2/3] docs: correct opencode login command usage --- apps/web/src/app/opencode/page.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/web/src/app/opencode/page.tsx b/apps/web/src/app/opencode/page.tsx index fa0708e..be5a981 100644 --- a/apps/web/src/app/opencode/page.tsx +++ b/apps/web/src/app/opencode/page.tsx @@ -9,7 +9,7 @@ import { opencodePluginVersion } from "@/lib/opencode-version"; const npmUrl = "https://www.npmjs.com/package/@sketchi-app/opencode-excalidraw"; const githubUrl = "https://github.com/anand-testcompare/sketchi/tree/main/packages/opencode-excalidraw"; -const authCommand = "opencode auth login sketchi"; +const authCommand = "opencode auth login"; const webCommand = "opencode web"; const cliCommand = "opencode"; @@ -285,7 +285,8 @@ export default function OpenCodeDocsPage() { .
  • - Run {authCommand} once (device-flow sign-in). + Run {authCommand} and select sketchi{" "} + (device-flow sign-in).
  • Run {webCommand}. From 77ed0d0d62783afc7cd3f01d3b421600e072cdc7 Mon Sep 17 00:00:00 2001 From: anandpant Date: Mon, 2 Mar 2026 12:46:21 -0600 Subject: [PATCH 3/3] fix(opencode-excalidraw): canonicalize api base to www --- packages/opencode-excalidraw/README.md | 2 +- .../opencode-excalidraw/src/index.test.ts | 52 +++++++++++++++++++ packages/opencode-excalidraw/src/index.ts | 15 +++++- 3 files changed, 66 insertions(+), 3 deletions(-) diff --git a/packages/opencode-excalidraw/README.md b/packages/opencode-excalidraw/README.md index ddc8e84..f8d3203 100644 --- a/packages/opencode-excalidraw/README.md +++ b/packages/opencode-excalidraw/README.md @@ -44,7 +44,7 @@ When this plugin is loaded, it registers a `sketchi-diagram` subagent (without d Optional env override: -- `SKETCHI_API_URL` (defaults to `https://sketchi.app`) +- `SKETCHI_API_URL` (defaults to `https://www.sketchi.app`) - `SKETCHI_ALLOW_UNSAFE_OUTPUT_PATH=1` to opt out of default output-path containment (power users only) Authentication is required for diagram APIs. In OpenCode, select auth provider `sketchi`: diff --git a/packages/opencode-excalidraw/src/index.test.ts b/packages/opencode-excalidraw/src/index.test.ts index b004afb..7863cd0 100644 --- a/packages/opencode-excalidraw/src/index.test.ts +++ b/packages/opencode-excalidraw/src/index.test.ts @@ -169,6 +169,58 @@ describe("SketchiPlugin", () => { expect(method?.label.toLowerCase()).toContain("device flow"); }); + test("canonicalizes sketchi.app API base to www for auth device start", async () => { + const originalFetch = globalThis.fetch; + const originalApiBase = process.env.SKETCHI_API_URL; + const requestedUrls: string[] = []; + + globalThis.fetch = ((input) => { + let url: string; + if (typeof input === "string") { + url = input; + } else if (input instanceof URL) { + url = input.toString(); + } else { + url = input.url; + } + requestedUrls.push(url); + return new Response( + JSON.stringify({ + deviceCode: "device-code", + userCode: "ABCD-EFGH", + interval: 5, + expiresIn: 600, + verificationUrl: "https://www.sketchi.app/device", + }), + { + status: 200, + headers: { "content-type": "application/json" }, + } + ); + }) as typeof fetch; + + process.env.SKETCHI_API_URL = "https://sketchi.app/"; + + try { + const plugin = await SketchiPlugin(createPluginInput()); + const method = plugin.auth?.methods?.[0]; + expect(method?.type).toBe("oauth"); + + const authStart = await method?.authorize(); + expect(authStart?.url).toBe("https://www.sketchi.app/device"); + expect(requestedUrls[0]).toBe( + "https://www.sketchi.app/api/auth/device/start" + ); + } finally { + globalThis.fetch = originalFetch; + if (originalApiBase === undefined) { + process.env.SKETCHI_API_URL = undefined; + } else { + process.env.SKETCHI_API_URL = originalApiBase; + } + } + }); + test("diagram_grade blocks concurrent calls for the same message", async () => { const deferred = createDeferred<{ data: { parts: Array<{ type: string; text: string }> }; diff --git a/packages/opencode-excalidraw/src/index.ts b/packages/opencode-excalidraw/src/index.ts index c46460a..2cec2bf 100644 --- a/packages/opencode-excalidraw/src/index.ts +++ b/packages/opencode-excalidraw/src/index.ts @@ -13,7 +13,7 @@ import { closeBrowser, renderElementsToPng } from "./lib/render"; import { resolveExcalidrawFromShareUrl } from "./lib/resolve-share-url"; import { createToolTraceId } from "./lib/trace"; -const DEFAULT_API_BASE = "https://sketchi.app"; +const DEFAULT_API_BASE = "https://www.sketchi.app"; const DEFAULT_OAUTH_TOKEN_TTL_MS = 60 * 60 * 1000; const DEVICE_FLOW_POLLING_SAFETY_MARGIN_MS = 1000; const TRAILING_SLASH_PATTERN = /\/$/; @@ -149,7 +149,18 @@ function completeGradeCall(key: string): void { } function normalizeApiBase(value: string): string { - return value.replace(TRAILING_SLASH_PATTERN, ""); + const trimmed = value.trim(); + const withoutTrailingSlash = trimmed.replace(TRAILING_SLASH_PATTERN, ""); + + try { + const parsed = new URL(withoutTrailingSlash); + if (parsed.hostname === "sketchi.app") { + parsed.hostname = "www.sketchi.app"; + } + return parsed.toString().replace(TRAILING_SLASH_PATTERN, ""); + } catch { + return withoutTrailingSlash; + } } function extractMessageText(