diff --git a/.github/workflows/pull_request.yaml b/.github/workflows/pull_request.yaml
index a7132b3..d29ae8f 100644
--- a/.github/workflows/pull_request.yaml
+++ b/.github/workflows/pull_request.yaml
@@ -49,6 +49,9 @@ jobs:
- run: yarn install --frozen-lockfile --network-timeout 120000
- run: yarn run typecheck
- run: yarn run lint
+ # Windows checkouts get CRLF line endings, which prettier reports as unformatted.
+ - run: yarn run format:check
+ if: runner.os == 'Linux'
- run: yarn run build
- run: yarn run test
# Sanity-check `npm pack` only once (Linux + Node 24) to avoid
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000..2e8551b
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,4 @@
+.scoria/
+scoria.config.json
+dist/
+coverage/
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..91a3517
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,3 @@
+{
+ "printWidth": 160
+}
diff --git a/demo/App.vue b/demo/App.vue
index e05893e..dfe9989 100644
--- a/demo/App.vue
+++ b/demo/App.vue
@@ -36,12 +36,7 @@
View Component
-
+
Sent Message:
diff --git a/demo/markdownArgs.ts b/demo/markdownArgs.ts
index 06a89fa..21fb257 100644
--- a/demo/markdownArgs.ts
+++ b/demo/markdownArgs.ts
@@ -1,7 +1,6 @@
import type { MarkdownArgs } from "../src/core/types";
-const isRecord = (value: unknown): value is Record
=>
- typeof value === "object" && value !== null && !Array.isArray(value);
+const isRecord = (value: unknown): value is Record => typeof value === "object" && value !== null && !Array.isArray(value);
export const isMarkdownArgs = (value: unknown): value is MarkdownArgs =>
isRecord(value) &&
diff --git a/package.json b/package.json
index e869d2c..f8829c3 100644
--- a/package.json
+++ b/package.json
@@ -33,7 +33,9 @@
"preview": "vite preview",
"test": "tsx --test tests/*.test.ts",
"quality": "scoria",
- "test:coverage": "c8 yarn run test"
+ "test:coverage": "c8 yarn run test",
+ "format": "prettier --write \"{src,demo,tests}/**/*.{ts,tsx,vue,json,css}\" \"*.{ts,js,json}\"",
+ "format:check": "prettier --check \"{src,demo,tests}/**/*.{ts,tsx,vue,json,css}\" \"*.{ts,js,json}\""
},
"resolutions": {
"minimatch": "^10.2.4"
@@ -53,6 +55,7 @@
"eslint-plugin-vue": "^10.11.1",
"globals": "^17.13.0",
"gui-chat-protocol": "^2.3.0",
+ "prettier": "^3.9.10",
"scoria": "^0.4.1",
"tailwindcss": "^4.3.3",
"tsx": "^4.23.15",
diff --git a/src/core/hostResponse.ts b/src/core/hostResponse.ts
index fd7412a..dd45986 100644
--- a/src/core/hostResponse.ts
+++ b/src/core/hostResponse.ts
@@ -7,29 +7,17 @@ export interface SaveImagesResponse {
imageUrls?: Record;
}
-const isRecord = (value: unknown): value is Record =>
- typeof value === "object" && value !== null;
+const isRecord = (value: unknown): value is Record => typeof value === "object" && value !== null;
-const isOptionalString = (value: unknown): value is string | undefined =>
- value === undefined || typeof value === "string";
+const isOptionalString = (value: unknown): value is string | undefined => value === undefined || typeof value === "string";
-const isStringRecord = (value: unknown): value is Record =>
- isRecord(value) &&
- Object.values(value).every((item) => typeof item === "string");
+const isStringRecord = (value: unknown): value is Record => isRecord(value) && Object.values(value).every((item) => typeof item === "string");
-export const isGeneratedImageResponse = (
- value: unknown,
-): value is GeneratedImageResponse =>
- isRecord(value) &&
- typeof value.success === "boolean" &&
- isOptionalString(value.imageData);
+export const isGeneratedImageResponse = (value: unknown): value is GeneratedImageResponse =>
+ isRecord(value) && typeof value.success === "boolean" && isOptionalString(value.imageData);
-export const isSaveImagesResponse = (
- value: unknown,
-): value is SaveImagesResponse =>
- isRecord(value) &&
- (value.imageUrls === undefined || isStringRecord(value.imageUrls));
+export const isSaveImagesResponse = (value: unknown): value is SaveImagesResponse =>
+ isRecord(value) && (value.imageUrls === undefined || isStringRecord(value.imageUrls));
/** `loadBlankImageBase64` returns the reference image; anything else is "no reference". */
-export const readBlankImageBase64 = (value: unknown): string =>
- typeof value === "string" ? value : "";
+export const readBlankImageBase64 = (value: unknown): string => (typeof value === "string" ? value : "");
diff --git a/src/core/plugin.ts b/src/core/plugin.ts
index 5cc87fa..d530cb4 100644
--- a/src/core/plugin.ts
+++ b/src/core/plugin.ts
@@ -1,19 +1,12 @@
import type { ToolContext, ToolPluginCore } from "gui-chat-protocol";
import type { MarkdownArgs, MarkdownToolData, MarkdownResult } from "./types";
-import {
- isGeneratedImageResponse,
- isSaveImagesResponse,
- readBlankImageBase64,
-} from "./hostResponse";
+import { isGeneratedImageResponse, isSaveImagesResponse, readBlankImageBase64 } from "./hostResponse";
import { TOOL_NAME, TOOL_DEFINITION } from "./definition";
// context is nullable on purpose: hosts that run the plugin without client-side
// state (MulmoClaude's server bridge) pass an empty or missing context, and
// reading through it unguarded threw a TypeError instead of returning a result.
-export const presentDocument = async (
- context: ToolContext | null | undefined,
- args: MarkdownArgs,
-): Promise => {
+export const presentDocument = async (context: ToolContext | null | undefined, args: MarkdownArgs): Promise => {
let { markdown } = args;
const { title, filenameHint } = args;
const app = context?.app;
@@ -34,9 +27,7 @@ export const presentDocument = async (
const images: Record = {};
// Load blank image for aspect ratio reference
- const blankImageBase64 = app.loadBlankImageBase64
- ? readBlankImageBase64(await app.loadBlankImageBase64())
- : "";
+ const blankImageBase64 = app.loadBlankImageBase64 ? readBlankImageBase64(await app.loadBlankImageBase64()) : "";
// Generate images for each placeholder in parallel
const imagePromises = matches.map(async (match, i) => {
@@ -44,11 +35,7 @@ export const presentDocument = async (
const imageId = `image_${i}`;
try {
- const result = await app!.generateImageWithBackend!(
- prompt,
- blankImageBase64 ? [blankImageBase64] : [],
- context,
- );
+ const result = await app!.generateImageWithBackend!(prompt, blankImageBase64 ? [blankImageBase64] : [], context);
if (isGeneratedImageResponse(result) && result.success && result.imageData) {
images[imageId] = `data:image/png;base64,${result.imageData}`;
@@ -87,8 +74,7 @@ export const presentDocument = async (
title,
data: { markdown, filenameHint },
uuid: docUuid,
- instructions:
- "Acknowledge that the document has been created and is displayed to the user.",
+ instructions: "Acknowledge that the document has been created and is displayed to the user.",
};
}
@@ -96,8 +82,7 @@ export const presentDocument = async (
message: `Created markdown document: ${title}`,
title,
data: { markdown, filenameHint },
- instructions:
- "Acknowledge that the document has been created and is displayed to the user.",
+ instructions: "Acknowledge that the document has been created and is displayed to the user.",
};
};
@@ -105,8 +90,7 @@ export const pluginCore: ToolPluginCore
toolDefinition: TOOL_DEFINITION,
execute: presentDocument,
generatingMessage: "Creating document...",
- waitingMessage:
- "Tell the user that the document was created, will be presented to the user shortly.",
+ waitingMessage: "Tell the user that the document was created, will be presented to the user shortly.",
isEnabled: () => true,
backends: ["imageGen"],
};
diff --git a/src/vue/View.vue b/src/vue/View.vue
index b50a8fa..ec0d50e 100644
--- a/src/vue/View.vue
+++ b/src/vue/View.vue
@@ -1,9 +1,6 @@
-
+
No markdown content available
@@ -20,27 +17,14 @@
-
+
Edit Markdown Source
-
-
+
+
diff --git a/src/vue/index.ts b/src/vue/index.ts
index 7f50e59..d9b58b9 100644
--- a/src/vue/index.ts
+++ b/src/vue/index.ts
@@ -34,11 +34,7 @@ export const plugin: ToolPlugin = {
export type { MarkdownToolData, MarkdownArgs, MarkdownResult } from "../core/types";
-export {
- TOOL_DEFINITION,
- executeMarkdown,
- pluginCore,
-} from "../core/plugin";
+export { TOOL_DEFINITION, executeMarkdown, pluginCore } from "../core/plugin";
export { samples } from "../core/samples";
diff --git a/tests/hostResponse.test.ts b/tests/hostResponse.test.ts
index 18f438d..669d9d1 100644
--- a/tests/hostResponse.test.ts
+++ b/tests/hostResponse.test.ts
@@ -7,18 +7,11 @@
import { test, describe } from "node:test";
import assert from "node:assert";
-import {
- isGeneratedImageResponse,
- isSaveImagesResponse,
- readBlankImageBase64,
-} from "../src/core/hostResponse.js";
+import { isGeneratedImageResponse, isSaveImagesResponse, readBlankImageBase64 } from "../src/core/hostResponse.js";
describe("isGeneratedImageResponse", () => {
test("accepts a successful response carrying image data", () => {
- assert.equal(
- isGeneratedImageResponse({ success: true, imageData: "base64" }),
- true,
- );
+ assert.equal(isGeneratedImageResponse({ success: true, imageData: "base64" }), true);
});
test("accepts a failure response with no image data", () => {
@@ -31,21 +24,14 @@ describe("isGeneratedImageResponse", () => {
test("rejects values that are not a response object", () => {
[null, undefined, "ok", 7].forEach((value) => {
- assert.equal(
- isGeneratedImageResponse(value),
- false,
- `should reject ${JSON.stringify(value)}`,
- );
+ assert.equal(isGeneratedImageResponse(value), false, `should reject ${JSON.stringify(value)}`);
});
});
});
describe("isSaveImagesResponse", () => {
test("accepts a map of image id to url", () => {
- assert.equal(
- isSaveImagesResponse({ imageUrls: { image_0: "/a.png", image_1: "/b.png" } }),
- true,
- );
+ assert.equal(isSaveImagesResponse({ imageUrls: { image_0: "/a.png", image_1: "/b.png" } }), true);
});
test("accepts a response with no imageUrls key", () => {
@@ -58,11 +44,7 @@ describe("isSaveImagesResponse", () => {
test("rejects values that are not a response object", () => {
[null, undefined, "ok", 7].forEach((value) => {
- assert.equal(
- isSaveImagesResponse(value),
- false,
- `should reject ${JSON.stringify(value)}`,
- );
+ assert.equal(isSaveImagesResponse(value), false, `should reject ${JSON.stringify(value)}`);
});
});
});
diff --git a/tests/plugin.test.ts b/tests/plugin.test.ts
index 3111bfa..effd50a 100644
--- a/tests/plugin.test.ts
+++ b/tests/plugin.test.ts
@@ -23,14 +23,8 @@ const CONTEXTS: [string, unknown][] = [
describe("GUIChatPluginMarkdown - host provides no context", () => {
CONTEXTS.forEach(([label, context]) => {
test(`returns a result instead of throwing when context is ${label}`, async () => {
- const result = await pluginCore.execute(
- context as never,
- ARGS as never,
- );
- assert.ok(
- result && typeof result === "object",
- "execute must resolve to a ToolResult rather than throw",
- );
+ const result = await pluginCore.execute(context as never, ARGS as never);
+ assert.ok(result && typeof result === "object", "execute must resolve to a ToolResult rather than throw");
});
});
});
diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json
index 2d3f8e8..dd7ce28 100644
--- a/tsconfig.eslint.json
+++ b/tsconfig.eslint.json
@@ -7,17 +7,6 @@
"declarationDir": null,
"jsx": "react-jsx"
},
- "include": [
- "*.ts",
- "src/**/*.ts",
- "src/**/*.tsx",
- "src/**/*.vue",
- "demo/**/*.ts",
- "demo/**/*.tsx",
- "demo/**/*.vue"
- ],
- "exclude": [
- "node_modules",
- "dist"
- ]
+ "include": ["*.ts", "src/**/*.ts", "src/**/*.tsx", "src/**/*.vue", "demo/**/*.ts", "demo/**/*.tsx", "demo/**/*.vue"],
+ "exclude": ["node_modules", "dist"]
}
diff --git a/vite.config.ts b/vite.config.ts
index ba79970..2342c4d 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -14,8 +14,7 @@ export default defineConfig({
},
name: "GUIChatPluginMarkdown",
formats: ["es", "cjs"],
- fileName: (format, entryName) =>
- `${entryName}.${format === "es" ? "js" : "cjs"}`,
+ fileName: (format, entryName) => `${entryName}.${format === "es" ? "js" : "cjs"}`,
},
rollupOptions: {
external: ["vue"],
diff --git a/yarn.lock b/yarn.lock
index 872c4be..11614f7 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3000,6 +3000,11 @@ prelude-ls@^1.2.1:
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
+prettier@^3.9.10:
+ version "3.9.10"
+ resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.9.10.tgz#ac378e432f1a7381518f2e080816bbaefa38d585"
+ integrity sha512-skGHTH1Yb9QvaTC4hWnRTzEYtCpApF0UMBPz+t/0ZPzP8ihefo/btKd+QA7pUE0fEwA1SbS/NmhyEcstdXOIdA==
+
pretty-ms@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/pretty-ms/-/pretty-ms-7.0.1.tgz#7d903eaab281f7d8e03c66f867e239dc32fb73e8"