diff --git a/apps/web/src/app/opencode/page.tsx b/apps/web/src/app/opencode/page.tsx
index 1291295..be5a981 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";
const webCommand = "opencode web";
const cliCommand = "opencode";
@@ -284,11 +285,15 @@ export default function OpenCodeDocsPage() {
.
- Run opencode web.
+ Run {authCommand} and select sketchi{" "}
+ (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.
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(