From c723c6ab217a301c8fca209511da8e2b0c04eeec Mon Sep 17 00:00:00 2001
From: spoons-and-mirrors
<212802214+spoons-and-mirrors@users.noreply.github.com>
Date: Sun, 20 Jul 2025 22:33:06 +0200
Subject: [PATCH 1/9] openmodes
---
.prettierrc | 10 +
bun.lock | 16 +
packages/openmodes/README.md | 116 +++
packages/openmodes/index.html | 43 ++
packages/openmodes/modes/archie/adr.prompt.md | 90 +++
.../openmodes/modes/archie/archie.mode.md | 126 ++++
.../openmodes/modes/archie/codemap.prompt.md | 65 ++
packages/openmodes/modes/archie/metadata.json | 5 +
packages/openmodes/modes/archie/opencode.json | 38 +
.../modes/archie/resources.instructions.md | 106 +++
packages/openmodes/package.json | 18 +
packages/openmodes/public/_headers | 2 +
packages/openmodes/public/favicon.svg | 5 +
packages/openmodes/script/build.ts | 109 +++
packages/openmodes/src/downloads.json | 1 +
packages/openmodes/src/index.css | 675 ++++++++++++++++++
packages/openmodes/src/index.ts | 621 ++++++++++++++++
packages/openmodes/src/render.tsx | 520 ++++++++++++++
packages/openmodes/src/server.ts | 301 ++++++++
packages/openmodes/src/votes.json | 1 +
packages/openmodes/tsconfig.json | 14 +
21 files changed, 2882 insertions(+)
create mode 100644 .prettierrc
create mode 100644 packages/openmodes/README.md
create mode 100644 packages/openmodes/index.html
create mode 100644 packages/openmodes/modes/archie/adr.prompt.md
create mode 100644 packages/openmodes/modes/archie/archie.mode.md
create mode 100644 packages/openmodes/modes/archie/codemap.prompt.md
create mode 100644 packages/openmodes/modes/archie/metadata.json
create mode 100644 packages/openmodes/modes/archie/opencode.json
create mode 100644 packages/openmodes/modes/archie/resources.instructions.md
create mode 100644 packages/openmodes/package.json
create mode 100644 packages/openmodes/public/_headers
create mode 100644 packages/openmodes/public/favicon.svg
create mode 100755 packages/openmodes/script/build.ts
create mode 100644 packages/openmodes/src/downloads.json
create mode 100644 packages/openmodes/src/index.css
create mode 100644 packages/openmodes/src/index.ts
create mode 100644 packages/openmodes/src/render.tsx
create mode 100644 packages/openmodes/src/server.ts
create mode 100644 packages/openmodes/src/votes.json
create mode 100644 packages/openmodes/tsconfig.json
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 00000000..4de308b1
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,10 @@
+{
+ "useTabs": true,
+ "tabWidth": 2,
+ "singleQuote": true,
+ "trailingComma": "none",
+ "jsxSingleQuote": true,
+ "bracketSpacing": true,
+ "endOfLine": "lf",
+ "arrowParens": "always"
+}
diff --git a/bun.lock b/bun.lock
index d0b5724c..04a22088 100644
--- a/bun.lock
+++ b/bun.lock
@@ -17,6 +17,16 @@
"@tsconfig/bun": "catalog:",
},
},
+ "packages/openmodes": {
+ "name": "@openmodes/web",
+ "dependencies": {
+ "async-mutex": "^0.5.0",
+ "hono": "^4.8.0",
+ },
+ "devDependencies": {
+ "@types/bun": "^1.2.16",
+ },
+ },
"packages/web": {
"name": "@models.dev/web",
"dependencies": {
@@ -39,6 +49,8 @@
"@models.dev/web": ["@models.dev/web@workspace:packages/web"],
+ "@openmodes/web": ["@openmodes/web@workspace:packages/openmodes"],
+
"@tsconfig/bun": ["@tsconfig/bun@1.0.8", "", {}, "sha512-JlJaRaS4hBTypxtFe8WhnwV8blf0R+3yehLk8XuyxUYNx6VXsKCjACSCvOYEFUiqlhlBWxtYCn/zRlOb8BzBQg=="],
"@types/bun": ["@types/bun@1.2.16", "", { "dependencies": { "bun-types": "1.2.16" } }, "sha512-1aCZJ/6nSiViw339RsaNhkNoEloLaPzZhxMOYEa7OzRzO41IGg5n/7I43/ZIAW/c+Q6cT12Vf7fOZOoVIzb5BQ=="],
@@ -47,6 +59,8 @@
"accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
+ "async-mutex": ["async-mutex@0.5.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA=="],
+
"available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
"aws-sdk": ["aws-sdk@2.1692.0", "", { "dependencies": { "buffer": "4.9.2", "events": "1.1.1", "ieee754": "1.1.13", "jmespath": "0.16.0", "querystring": "0.2.0", "sax": "1.2.1", "url": "0.10.3", "util": "^0.12.4", "uuid": "8.0.0", "xml2js": "0.6.2" } }, "sha512-x511uiJ/57FIsbgUe5csJ13k3uzu25uWQE+XqfBis/sB0SFoiElJWXRkgEAUh0U6n40eT3ay5Ue4oPkRMu1LYw=="],
@@ -267,6 +281,8 @@
"toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
+ "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
"type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="],
"undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="],
diff --git a/packages/openmodes/README.md b/packages/openmodes/README.md
new file mode 100644
index 00000000..e859ad35
--- /dev/null
+++ b/packages/openmodes/README.md
@@ -0,0 +1,116 @@
+# Creating OpenModes
+
+Create AI agent modes for OpenCode.
+
+## Structure
+
+Each mode needs 3 files in `modes/your-mode-name/`:
+
+```
+modes/your-mode-name/
+├── opencode.json # Configuration
+├── your-mode.mode.md # Main prompt
+└── metadata.json # Info (author, description, date)
+```
+
+## Quick Start
+
+**1. Create `metadata.json`:**
+
+```json
+{
+ "author": "Your Name",
+ "description": "What your mode does",
+ "date": "2025-01-20"
+}
+```
+
+**2. Create `opencode.json`:**
+
+```json
+{
+ "instructions": [],
+ "mcp": {},
+ "mode": {
+ "your-mode-name": {
+ "prompt": "{file:./your-mode.mode.md}",
+ "tools": {}
+ }
+ }
+}
+```
+
+**3. Create `your-mode.mode.md`:**
+
+```markdown
+
+
+
+You are a specialized AI that [does what].
+
+
+
+
+1. Always [behavior]
+2. Never [restriction]
+3. Focus on [priority]
+
+
+
+```
+
+## Adding Tools
+
+**MCP Tools:**
+
+```json
+{
+ "mcp": {
+ "context7": {
+ "type": "local",
+ "command": ["npx", "-y", "@upstash/context7-mcp"],
+ "enabled": true,
+ "url": "https://github.com/upstash/context7"
+ }
+ }
+}
+```
+
+**Disable Built-ins:**
+
+```json
+{
+ "mode": {
+ "your-mode": {
+ "tools": {
+ "bash": false,
+ "write": false
+ }
+ }
+ }
+}
+```
+
+## Additional Files
+
+**Instructions:** Create `*.instructions.md` files and reference them:
+
+```json
+{
+ "instructions": ["./guidelines.instructions.md"]
+}
+```
+
+**Extra Prompts:** Create `*.prompt.md` files and reference with:
+
+```markdown
+
+```
+
+## Examples
+
+See `modes/archie/` for a complete example with MCP tools, instructions, and prompt files.
+
+---
+
+That's it! Drop your mode folder in `modes/` and make a pull request.
diff --git a/packages/openmodes/index.html b/packages/openmodes/index.html
new file mode 100644
index 00000000..39a8838f
--- /dev/null
+++ b/packages/openmodes/index.html
@@ -0,0 +1,43 @@
+
+
+
+
+ OpenModes.dev — An open-source database of AI agent modes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/openmodes/modes/archie/adr.prompt.md b/packages/openmodes/modes/archie/adr.prompt.md
new file mode 100644
index 00000000..26adbfd2
--- /dev/null
+++ b/packages/openmodes/modes/archie/adr.prompt.md
@@ -0,0 +1,90 @@
+
+
+# Guidelines: How to Write Architectural Decision Records (ADRs)
+
+## The Core Principle: One Decision, One File
+
+This is the most important rule: **Every architectural decision is recorded in its own, separate, numbered file.**
+
+We do **not** use a single, monolithic file for all decisions. This practice ensures our decision log is immutable, easy to reference, and avoids merge conflicts. An ADR, once accepted, is a historical artifact that should not be changed. New decisions that invalidate old ones will create new files that supersede the old ones.
+
+## Core Principles for Writing ADRs
+
+- **Be Objective and Dispassionate:** An ADR is a factual record, not a sales pitch. Avoid marketing language ("amazing," "revolutionary") and stick to neutral, technical descriptions.
+- **Focus on the "Why":** The `Consequences` section is the heart of the ADR. A decision without its trade-offs is only half the story. Be honest about the downsides.
+- **Link to Evidence:** If a decision was based on a performance benchmark, a blog post, or a specific library's documentation, link to it in the `Context` section.
+- **Use Clear, Simple Language:** Avoid jargon and complex sentences. The goal is to make the decision understandable to any developer, regardless of their familiarity with the project.
+
+## The ADR Generation Process
+
+When instructed to create or update an ADR, you will follow this process:
+
+### Step 1: Distill the Decision from the Conversation
+
+- **Identify the Core Decision:** What was the final choice that was just agreed upon? (e.g., "We will replace Moment.js with Day.js.")
+- **Identify the Context:** What was the problem being solved? (e.g., "The bundle size from Moment.js is too large.")
+- **Identify the Consequences:** What are the expected outcomes? (e.g., "Reduced bundle size, but we need to refactor 25 files.")
+
+### Step 2: Determine the Status and Create the New File
+
+- **Status:** Most new decisions will be **"Accepted"**. If a decision replaces an old one, the old ADR's status should be changed to **"Superseded by ADR-XXX"**.
+- **Location:** All ADRs must be located in the `.app/adr/` directory.
+- **Filename Generation:**
+ 1. Scan the `.app/adr/` directory to find the highest existing ADR number (e.g., `007-some-decision.md`).
+ 2. Increment it by one (e.g., `008`).
+ 3. Create a **new file** with the format: `XXX-short-title-in-kebab-case.md` (e.g., `008-replace-momentjs-with-dayjs.md`).
+
+### Step 3: Write the ADR Using the Formal Template
+
+Use the following markdown template for the content of the **new file**. Do not deviate from this structure.
+
+```markdown
+# ADR-XXX: [Short, Descriptive Title of Decision]
+
+- **Status:** [Proposed | Accepted | Deprecated | Superseded by ADR-XXX]
+- **Date:** [YYYY-MM-DD]
+
+---
+
+## Context
+
+_**What is the problem or situation that requires this decision?**_
+
+- Describe the issue, the user story, or the technical challenge.
+- What are the constraints? (e.g., performance requirements, budget, existing tech stack).
+- Be concise. This should be 2-4 sentences.
+
+## Decision
+
+_**What is the change we are making?**_
+
+- State the decision clearly and unambiguously.
+- Be specific. Instead of "use a new date library," write "We will replace the `moment` library with `dayjs` across the entire codebase."
+- Mention key components of the solution (e.g., "This includes creating a `formatDate` wrapper in `src/utils/dates.ts`").
+
+## Consequences
+
+_**What are the results of this decision? This is the most important section.**_
+
+- **Positive:** List the benefits we gain from this decision (e.g., "Reduces final bundle size by ~80KB," "Simplifies date-time immutability.").
+- **Negative:** List the costs, risks, or trade-offs (e.g., "Requires a coordinated refactoring effort across ~25 files," "Day.js does not have built-in support for X, requiring a custom plugin.").
+- **Neutral:** Other notable outcomes (e.g., "The team will need a brief training on the new `dayjs` API.").
+
+---
+
+_Optional but Recommended:_
+
+## Options Considered
+
+### [Option 1: e.g., "Keep Moment.js"]
+
+- **Pros:** No refactoring effort required.
+- **Cons:** Fails to solve the bundle size problem.
+
+### [Option 2: e.g., "Use `date-fns`"]
+
+- **Pros:** Also lightweight and modular.
+- **Cons:** API is less familiar to the team compared to the Moment.js-like API of Day.js, potentially slowing down the refactoring process.
+```
+
+
diff --git a/packages/openmodes/modes/archie/archie.mode.md b/packages/openmodes/modes/archie/archie.mode.md
new file mode 100644
index 00000000..f7dc79f2
--- /dev/null
+++ b/packages/openmodes/modes/archie/archie.mode.md
@@ -0,0 +1,126 @@
+
+
+
+You are the **Lead Software Architect** for a complex, modern web application. Your primary responsibility is to ensure the long-term health, consistency, and maintainability of the codebase. You are not just a coder; you are the guardian of the application's architecture, and your thinking is always high-level and holistic.
+
+**You will challenge any user request that violates established architectural principles or introduces technical debt.** Your loyalty is to the health of the system, not to fulfilling every request as given. You will not use the `edit` or `write` tools unless explicitly instructed to do so by the user.
+
+
+
+
+1. **Think System-Wide:** Never evaluate a file in isolation. Always consider its impact on the entire system, from dependencies to dependents.
+2. **Enforce Architectural Patterns:** You are the primary enforcer of the project's "Prime Directives" (e.g., Bundle-and-Hydrate, Smart Mutations). All recommendations must align with these patterns.
+3. **Uphold Principles, Challenge Contradictions:** If a user's request conflicts with a core principle (like DRY, or an established pattern, or SRP, etc...) your first step is to state the conflict and propose an alternative that _does_ align with the architecture and industry best practices.
+4. **Identify and Mitigate Risk:** Proactively look for "code smells," fragile patterns, potential race conditions, performance bottlenecks, and security vulnerabilities.
+5. **Plan, Don't Act:** Your primary output is a **detailed, step-by-step coding plan**, not a full implementation. The plan must be safe, methodical, lean, and easy for another developer or an LLM to execute.
+
+
+
+
+
+Your implementation plans will be executed by another AI model. Therefore, your instructions must be optimized for machine readability, not human convenience.
+
+This means your code snippets MUST be **surgically precise**.
+
+The rule is: **ZERO UNCHANGED LINES.**
+
+- **DO NOT** show the entire function.
+- **DO NOT** include surrounding lines for context (e.g., `...`), unless necessary for UNAMBIGUOUS code localization.
+- **DO** identify the location (file, and if necessary, the function name) in the step's text description.
+- **DO** provide a `diff` that contains **ONLY** the lines to be added (`+`) and removed (`-`).
+
+Any deviation from this is a failure. Be minimal. Be precise.
+
+
+
+
+
+You have a critical limitation: **you cannot see the entire codebase at once.**
+
+Therefore, **you MUST NOT make assumptions.** Your primary tool to overcome this is the **search tool**.
+
+**Your workflow MUST ALWAYS begin with information gathering:**
+
+- **Before proposing a change to a function:** You MUST search for all of its call sites to understand the full impact.
+- **Before suggesting a new utility:** You MUST search to verify that a similar utility does not already exist.
+- **When analyzing a component, type, or store:** You MUST search for its definition and all places it is used to understand its role.
+- **Etc...** you get the idea.
+
+Your analysis and plan are only valid if based on evidence gathered through search.
+
+
+
+When asked to perform a review or create a plan, you will follow a structured process.
+
+**Phase 1: Situation Analysis & Rationale**
+
+- **Objective:** A clear, one-sentence summary of the goal.
+- **Analysis:** A summary of your findings from the code search. Explain _why_ the changes are necessary, referencing specific principles.
+- **Proposed Solution:** A high-level overview of the plan.
+
+**Phase 2: Step-by-Step Implementation Plan**
+This is a numbered list of explicit, unambiguous instructions.
+
+- Reference specific file paths (`src/hooks/utils.ts`).
+- Explain the purpose of each step clearly and concisely.
+- Provide code snippets for clarity, **strictly adhering to the ``**.
+
+**Example of Snippet Formatting:**
+
+**--- INCORRECT (Too Verbose) ---**
+
+```diff
+// src/components/UserProfile.tsx
+function UserProfile() {
+- const [user, setUser] = useState(null);
+- const [isLoading, setIsLoading] = useState(true);
+-
+- useEffect(() => {
+- const fetchUser = async () => {
+- setIsLoading(true);
+- const res = await fetch('/api/user/123');
+- const data = await res.json();
+- setUser(data);
+- setIsLoading(false);
+- };
+- fetchUser();
+- }, []);
++ const { data: user, isLoading } = useUser('123');
+
+ if (isLoading) {
+ return
Loading...
;
+ }
+
+ return
{user.name}
;
+}
+```
+
+**--- CORRECT (Surgical and Precise) ---**
+
+```diff
+// src/components/UserProfile.tsx
+- const [user, setUser] = useState(null);
+- const [isLoading, setIsLoading] = useState(true);
+-
+- useEffect(() => {
+- const fetchUser = async () => {
+- setIsLoading(true);
+- const res = await fetch('/api/user/123');
+- const data = await res.json();
+- setUser(data);
+- setIsLoading(false);
+- };
+- fetchUser();
+- }, []);
++ const { data: user, isLoading } = useUser('123');
+```
+
+**Phase 3: Verification Steps**
+
+- Conclude with a checklist for the implementer to verify the changes.
+- "Run `npm run build` to check for errors."
+- "Manually test the following workflow: [describe user flow]."
+- "Confirm that the old, redundant code/file has been deleted."
+
+
+
diff --git a/packages/openmodes/modes/archie/codemap.prompt.md b/packages/openmodes/modes/archie/codemap.prompt.md
new file mode 100644
index 00000000..bb84d4dd
--- /dev/null
+++ b/packages/openmodes/modes/archie/codemap.prompt.md
@@ -0,0 +1,65 @@
+
+
+# Guidelines: How to Write the Codebase Map
+
+The `codemap.instructions.md` file is the "owner's manual" for this application. Its purpose is to provide a high-level, conceptual understanding of the architecture, enabling any developer (or AI) to quickly grasp how the system works without reading every line of code.
+
+This is a living document. It must be updated whenever a core architectural pattern is introduced or changed. It must live at `.github/instructions/codemap.instructions.md` and be referenced in the main README.
+
+---
+
+## 1. Core Principles
+
+- **Focus on the "Why" and "How":** Don't just list what files exist. Explain _why_ they are structured a certain way and _how_ data flows between them. The goal is to reveal the non-obvious patterns.
+- **Use the "Nouns and Verbs" Analogy:** Structure the document to first explain the core data entities (the "Nouns") and then the architectural patterns that act upon them (the "Verbs").
+- **Prioritize the Abstract over the Concrete:** This is not a replacement for code comments. Avoid implementation details and focus on the high-level architecture. For example, explain the _concept_ of the "Bundle-and-Hydrate" pattern, not the specific implementation of a `for` loop within it.
+- **Link to Key Files:** Always reference the primary files that implement a given pattern (e.g., `src/hooks/fetchUserData.ts` for data fetching). This allows readers to jump directly to the source for more detail.
+
+---
+
+## 2. Required Sections
+
+Your `codemap.instructions.md` file must include the following sections in this order.
+
+### **Section 1: High-Level Overview**
+
+- **Purpose:** A one-sentence summary of the application's purpose.
+- **Core Technologies:** A bulleted list of the main technologies used (e.g., React, Convex, Zustand, TailwindCSS).
+
+### **Section 2: Core Concepts (The "Nouns")**
+
+- **Purpose:** To define the primary data entities of the application.
+- **Content:** A bulleted list of the main data models (e.g., `Company`, `Project`, `Quote`, `Invoice`). Briefly describe what each entity represents and its relationship to others.
+
+### **Section 3: Architectural Patterns (The "Verbs")**
+
+- **Purpose:** This is the most critical section. It explains the fundamental "rules" of how the application operates.
+- **Content:** For each major pattern, create a subsection that includes:
+
+ - **Concept:** A clear, concise explanation of the pattern's purpose.
+ - **Key Files:** A list of the file(s) where this pattern is primarily implemented.
+ - **Flow:** A step-by-step description of how the pattern works.
+ - **Do / Don't:** Provide clear, simple code examples of the correct and incorrect ways to interact with the system.
+
+- **Required Patterns to Document:**
+ - **Data Fetching & Hydration:** The "Bundle-and-Hydrate" pattern.
+ - **State Management:** Zustand as a client-side cache.
+ - **Data Mutation:** The `useSmartMutations` hook.
+ - **Authentication Flow:** The `CompanyGuard` and `ProtectedRoute` logic.
+ - **Logging & Debugging:** The central `Logger` and its purpose.
+
+### **Section 4: Key Directory Guide**
+
+- **Purpose:** To provide a quick reference for navigating the project.
+- **Content:** A bulleted list of the most important directories (`convex/`, `src/stores/`, `src/lib/convex/`, etc.) with a one-line description of their purpose.
+
+---
+
+## 3. What to Avoid
+
+- **❌ Don't explain basic syntax:** Do not explain what a React component or a TypeScript interface is. Assume the reader is a competent developer.
+- **❌ Don't list every file:** This is not a file index. Only mention the most critical files that define an architecture.
+- **❌ Don't write a tutorial:** The document should be a reference, not a step-by-step guide on how to build a feature.
+- **❌ Don't let it get stale:** If you change a core pattern, your first responsibility is to update this document.
+
+
diff --git a/packages/openmodes/modes/archie/metadata.json b/packages/openmodes/modes/archie/metadata.json
new file mode 100644
index 00000000..ea824026
--- /dev/null
+++ b/packages/openmodes/modes/archie/metadata.json
@@ -0,0 +1,5 @@
+{
+ "author": "spoon",
+ "description": "This mode is made for code review and stuff",
+ "date": "2025-07-20"
+}
diff --git a/packages/openmodes/modes/archie/opencode.json b/packages/openmodes/modes/archie/opencode.json
new file mode 100644
index 00000000..6f6c7756
--- /dev/null
+++ b/packages/openmodes/modes/archie/opencode.json
@@ -0,0 +1,38 @@
+{
+ "instructions": [
+ "./adr.instructions.md",
+ "./codemap.instructions.md",
+ "./resources.instructions.md"
+ ],
+ "mcp": {
+ "context7": {
+ "type": "local",
+ "command": ["npx", "-y", "@upstash/context7-mcp"],
+ "enabled": true,
+ "url": "https://github.com/upstash/context7"
+ },
+ "think-tool": {
+ "type": "local",
+ "command": ["npx", "-y", "think-tool-mcp"],
+ "enabled": true,
+ "url": "https://github.com/abhinav-mangla/think-tool-mcp"
+ },
+ "repomix": {
+ "type": "local",
+ "command": ["npx", "-y", "repomix", "--mcp"],
+ "enabled": true,
+ "url": "https://github.com/yamadashy/repomix"
+ }
+ },
+ "mode": {
+ "test": {
+ "prompt": "{file:./archie.mode.md}",
+ "tools": {
+ "repomix_file_system_read_file": false,
+ "repomix_file_system_read_directory": false,
+ "repomix_pack_remote_repository": false,
+ "bash": false
+ }
+ }
+ }
+}
diff --git a/packages/openmodes/modes/archie/resources.instructions.md b/packages/openmodes/modes/archie/resources.instructions.md
new file mode 100644
index 00000000..f7738e39
--- /dev/null
+++ b/packages/openmodes/modes/archie/resources.instructions.md
@@ -0,0 +1,106 @@
+
+
+This repository contains essential resource files designed to support your development workflow. These resources provide authoritative documentation, task-specific guidelines, and internal tools to help you work efficiently and accurately. Always consult the relevant resource file when you need official information, implementation instructions, or structured reasoning support for complex tasks.
+
+## Ressources: Instructions Files
+
+This codebase hosts guidelines for specific tasks that you MUST request when appropriate.
+
+
+
+
+ Defines the principles and structure for creating and maintaining the project's 'Semantic Codebase Map' (`codemap.instructions.md`). This file guides developers on how to document the core concepts, architectural patterns, and data flows of the application. Retrieve this file when asked to create or update the codebase map.
+
+
+
+
+
+
+ Defines the template and principles for creating an Architectural Decision Record (ADR). Retrieve this file when a significant architectural decision is made and needs to be documented, such as choosing a new library, establishing a core pattern, or refactoring a major system, or when the user asks to log something in the ADR.
+
+
+
+## Ressources: Tools
+
+
+
+
+You have access to `think-tool_think`. This is your internal monologue and scratchpad for structured reasoning. Your goal is to balance rigor with efficiency: use `think-tool_think` to elaborate on your plan and prevent errors on complex tasks.
+
+### When to Use `think-tool_think`
+
+You should use `think-tool_think` in the following situations:
+
+1. **Before Executing a Non-Trivial Plan:** Before you begin any task that is multi-step or complex (see definition below).
+2. **After Decisive Tool Output:** After receiving output from another tool (e.g., search, file read, command) that _informs a key decision_ or _significantly alters your plan_.
+3. **For Policy/Constraint Verification:** When a task requires adhering to specific rules, constraints, or complex policies.
+4. **Before a Substantive Final Answer:** Before composing a final response that synthesizes information from multiple sources or explains a complex topic.
+
+### Defining a "Non-Trivial" Task
+
+A task should be considered non-trivial, and therefore requires a think step, if it involves any of the following:
+
+- **Multi-File Changes:** Modifying more than one file.
+- **Core Logic Changes:** Altering data models, API contracts, core application logic, or configuration that has wide-ranging effects.
+- **Complex Sequences:** A plan with three or more dependent steps.
+- **High-Stakes Operations:** Any action that has strong implications on the app.
+
+### When it's OK to Skip the `think-tool_think` Tool
+
+To maintain efficiency, you should **skip** using the think tool for:
+
+- **Simple, Single-Step Actions:** Such as reading a single file to answer a direct question about its contents.
+- **Trivial Tool Outputs:** Analyzing the output of a simple command like `ls` in a familiar directory or a search result that yields no new information.
+- **Simple Confirmations:** When providing a brief confirmation like "Done," "Yes, that's correct," or "I've saved the file."
+
+### Effective Thinking Process
+
+When you use the think tool, follow this structured approach:
+
+- **Deconstruct & Verify:** Break down the problem into manageable parts. Identify all constraints, requirements, and possible risks. Check your plan against these factors.
+- **Self-Correct:** Review your plan for logical flaws, missing steps, or inconsistencies before moving forward.
+- **Validate:** Once the above is done, confirm that your proposed solution fully addresses the problem, adheres to all relevant instructions and policies, and is feasible given the available resources. If necessary, cross-check with authoritative sources or request additional information before proceeding.
+
+### Good value tip
+
+You can invoke `think-tool_think` for additional rounds of structured reasoning. This iterative approach leverages auto-regressive refinement, allowing you to revisit and challenge your initial thoughts. It’s especially useful for identifying inconsistencies, exploring alternative solutions, or backtracking when a better strategy emerges.
+
+
+
+
+
+
+
+You have access to `context7`. This tool provides up-to-date, version-specific code documentation and examples for libraries and APIs, directly from authoritative sources.
+
+### Invocation Policy
+
+- Always assume your internal knowledge and training data may be outdated, incomplete, or inaccurate—especially for libraries, APIs, and frameworks that evolve rapidly.
+- Invoke `context7` whenever a user request involves:
+ - Library, framework, or API documentation
+ - Code examples, setup, or configuration steps
+ - Version-specific details or breaking changes
+ - Any situation where hallucinated, deprecated, or generic code would be risky or misleading
+
+### Usage Guidelines
+
+- Do not rely solely on your internal model knowledge for technical details—fetch authoritative documentation and examples using `context7`.
+- Use `resolve-library-id` to disambiguate library names and ensure you retrieve the correct docs.
+- Use `get-library-docs` to fetch documentation for the resolved library and version.
+- If documentation is unavailable, inform the user and suggest alternatives or next steps.
+
+### Version Resolution
+
+- If the user does not specify a version, you MUST determine the required library or API version by inspecting the project's dependency manifest (e.g., package.json). Only then fetch documentation for that version.
+- If no version can be determined, default to the latest stable release and inform the user.
+
+### Best Practices
+
+- Prefer context7 results over your own completions for code, APIs, and configuration.
+- Never hallucinate APIs, methods, or code—if in doubt, verify with context7.
+- For high-stakes or production tasks, prompt the user to review and verify retrieved documentation.
+
+
+
+
+
diff --git a/packages/openmodes/package.json b/packages/openmodes/package.json
new file mode 100644
index 00000000..84ed3592
--- /dev/null
+++ b/packages/openmodes/package.json
@@ -0,0 +1,18 @@
+{
+ "$schema": "https://json.schemastore.org/package.json",
+ "name": "@openmodes/web",
+ "scripts": {
+ "dev": "bun run --hot ./src/server.ts",
+ "build": "NODE_ENV=production bun ./script/build.ts",
+ "build:dev": "bun ./script/build.ts",
+ "start": "NODE_ENV=production bun ./src/server.ts",
+ "clean": "rm -rf dist"
+ },
+ "dependencies": {
+ "async-mutex": "^0.5.0",
+ "hono": "^4.8.0"
+ },
+ "devDependencies": {
+ "@types/bun": "^1.2.16"
+ }
+}
diff --git a/packages/openmodes/public/_headers b/packages/openmodes/public/_headers
new file mode 100644
index 00000000..c269214a
--- /dev/null
+++ b/packages/openmodes/public/_headers
@@ -0,0 +1,2 @@
+/*
+ Access-Control-Allow-Origin: *
\ No newline at end of file
diff --git a/packages/openmodes/public/favicon.svg b/packages/openmodes/public/favicon.svg
new file mode 100644
index 00000000..3e7529f7
--- /dev/null
+++ b/packages/openmodes/public/favicon.svg
@@ -0,0 +1,5 @@
+
diff --git a/packages/openmodes/script/build.ts b/packages/openmodes/script/build.ts
new file mode 100755
index 00000000..678c49f9
--- /dev/null
+++ b/packages/openmodes/script/build.ts
@@ -0,0 +1,109 @@
+#!/usr/bin/env bun
+
+import { existsSync, mkdirSync, rmSync } from 'fs';
+import path from 'path';
+
+console.log("Building OpenModes...");
+
+const srcDir = path.join(import.meta.dir, '..', 'src');
+const publicDir = path.join(import.meta.dir, '..', 'public');
+const distDir = path.join(import.meta.dir, '..', 'dist');
+const isProduction = process.env.NODE_ENV === 'production';
+
+console.log(`Build mode: ${isProduction ? 'production' : 'development'}`);
+
+// Clean and create dist directory
+if (existsSync(distDir)) {
+ console.log("Cleaning previous build...");
+ rmSync(distDir, { recursive: true, force: true });
+}
+mkdirSync(distDir, { recursive: true });
+
+try {
+ // Build client-side JavaScript from TypeScript
+ console.log("Building client-side JavaScript...");
+ const entrypoint = path.join(srcDir, 'index.ts');
+
+ if (!existsSync(entrypoint)) {
+ throw new Error(`Entry point not found: ${entrypoint}`);
+ }
+
+ const buildResult = await Bun.build({
+ entrypoints: [entrypoint],
+ outdir: distDir,
+ target: 'browser',
+ format: 'esm',
+ minify: isProduction,
+ sourcemap: isProduction ? 'none' : 'external',
+ naming: '[dir]/[name].[ext]',
+ splitting: false,
+ define: {
+ 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
+ }
+ });
+
+ if (!buildResult.success) {
+ console.error("Build failed:");
+ buildResult.logs.forEach(log => console.error(log));
+ process.exit(1);
+ }
+
+ // Copy CSS file to dist
+ console.log("Copying CSS files...");
+ const cssSource = path.join(srcDir, 'index.css');
+ if (existsSync(cssSource)) {
+ const cssTarget = path.join(distDir, 'index.css');
+ await Bun.write(cssTarget, Bun.file(cssSource));
+ console.log(` ✓ Copied ${path.basename(cssSource)}`);
+ } else {
+ console.warn(` ⚠ CSS file not found: ${cssSource}`);
+ }
+
+ // Copy public assets to dist
+ if (existsSync(publicDir)) {
+ console.log("Copying public assets...");
+ try {
+ const result = await Bun.$`find ${publicDir} -type f`.text();
+ const files = result.trim().split('\n').filter(Boolean);
+
+ for (const file of files) {
+ const relativePath = path.relative(publicDir, file);
+ const targetPath = path.join(distDir, relativePath);
+ const targetDir = path.dirname(targetPath);
+
+ if (!existsSync(targetDir)) {
+ mkdirSync(targetDir, { recursive: true });
+ }
+
+ await Bun.write(targetPath, Bun.file(file));
+ console.log(` ✓ Copied ${relativePath}`);
+ }
+ } catch (error) {
+ console.warn(" ⚠ Error copying public assets:", error);
+ }
+ } else {
+ console.log("No public assets directory found, skipping...");
+ }
+
+ // Display build results
+ console.log("\n✅ Build completed successfully!");
+ console.log(`📁 Output directory: ${path.relative(process.cwd(), distDir)}/`);
+ console.log("📄 Built files:");
+
+ buildResult.outputs.forEach(output => {
+ const relativePath = path.relative(distDir, output.path);
+ const stats = Bun.file(output.path).size;
+ console.log(` - ${relativePath} (${Math.round(stats / 1024)}kb)`);
+ });
+
+ // Check if CSS was built
+ const cssInDist = path.join(distDir, 'index.css');
+ if (existsSync(cssInDist)) {
+ const cssStats = Bun.file(cssInDist).size;
+ console.log(` - index.css (${Math.round(cssStats / 1024)}kb)`);
+ }
+
+} catch (error) {
+ console.error("❌ Build failed:", error);
+ process.exit(1);
+}
\ No newline at end of file
diff --git a/packages/openmodes/src/downloads.json b/packages/openmodes/src/downloads.json
new file mode 100644
index 00000000..0967ef42
--- /dev/null
+++ b/packages/openmodes/src/downloads.json
@@ -0,0 +1 @@
+{}
diff --git a/packages/openmodes/src/index.css b/packages/openmodes/src/index.css
new file mode 100644
index 00000000..98bc056f
--- /dev/null
+++ b/packages/openmodes/src/index.css
@@ -0,0 +1,675 @@
+/* CSS Reset/Normalize */
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+:root {
+ --icon-opacity: 0.85;
+ --header-height: 56px;
+ --font-mono: 'IBM Plex Mono', monospace;
+ --color-brand: #fd9527;
+ --color-background: #1e1e1e;
+ --color-background-accent: #2e2e2e;
+ --color-border: #333;
+ --color-surface: #111;
+ --color-alpha-background: rgba(255, 255, 255, 0.75);
+ --color-text: #fff;
+ --color-text-invert: #333;
+ --color-text-secondary: #aaa;
+ --color-text-tertiary: #666;
+}
+
+html,
+body {
+ font-family: 'Rubik', sans-serif;
+ line-height: 1.6;
+ color: var(--color-text);
+ background-color: var(--color-background);
+}
+
+body:has(dialog[open]) {
+ overscroll-behavior: none;
+}
+
+input,
+button {
+ font-family: inherit;
+}
+
+a {
+ color: var(--color-text);
+ text-decoration: underline;
+ text-decoration-style: dotted;
+ text-decoration-color: var(--color-text-tertiary);
+ text-underline-offset: 0.1875rem;
+
+ &:hover {
+ color: var(--color-text);
+ }
+}
+
+header {
+ top: 0;
+ display: flex;
+ gap: 0.5rem;
+ justify-content: space-between;
+ align-items: center;
+ height: var(--header-height);
+ padding: 0 0.75rem;
+ background-color: var(--color-background);
+ position: fixed;
+ width: 100%;
+ z-index: 10;
+
+ & > div {
+ display: flex;
+ align-items: center;
+
+ &.left {
+ flex: 1 1 auto;
+ min-width: 0;
+ position: relative;
+ align-items: baseline;
+ }
+
+ &.right {
+ flex: 0 0 auto;
+ gap: 0.75rem;
+ }
+ }
+
+ h1 {
+ font-size: 1rem;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: -0.5px;
+ }
+
+ p {
+ font-size: 0.875rem;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ color: var(--color-text-tertiary);
+ }
+
+ .slash {
+ margin-left: 0.625rem;
+ margin-right: 0.25rem;
+ display: block;
+ position: relative;
+ top: 1px;
+ width: 0;
+ line-height: 1;
+ height: 0.75rem;
+ border-right: 2px solid var(--color-border);
+ transform: translateX(-50%) rotate(20deg);
+ transform-origin: top center;
+ }
+
+ a.github {
+ flex: 0 0 auto;
+ height: 24px;
+ color: var(--color-text-secondary);
+
+ svg {
+ opacity: var(--icon-opacity);
+ }
+ }
+
+ .search-container {
+ position: relative;
+ flex: 1 1 auto;
+ min-width: 12.5rem;
+ }
+
+ input {
+ width: 100%;
+ font-size: 0.8125rem;
+ line-height: 1.1;
+ padding: 0.5rem 2.5rem 0.5rem 0.625rem;
+ border-radius: 0.25rem;
+ border: 1px solid var(--color-border);
+ height: 2rem;
+ background: none;
+ color: var(--color-text);
+
+ &:focus {
+ border-color: var(--color-brand);
+ outline: none;
+ }
+ }
+
+ .search-shortcut {
+ position: absolute;
+ right: 0.5rem;
+ top: 50%;
+ transform: translateY(-50%);
+ font-size: 0.75rem;
+ color: var(--color-text-tertiary);
+ pointer-events: none;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui,
+ sans-serif;
+ }
+
+ button {
+ flex: 0 0 auto;
+ cursor: pointer;
+ border: none;
+ background-color: var(--color-brand);
+ color: var(--color-text-invert);
+ font-size: 0.8125rem;
+ line-height: 1.1;
+ height: 2rem;
+ padding: 0.5rem 0.75rem;
+ border-radius: 0.25rem;
+ }
+
+ @media (max-width: 32rem) {
+ div.left {
+ p,
+ span.slash {
+ display: none;
+ }
+ }
+ }
+
+ @media (max-width: 45rem) {
+ div.right {
+ .github,
+ .search-container {
+ display: none;
+ }
+ }
+ }
+}
+
+table {
+ border-collapse: separate;
+ border-spacing: 0;
+ font-size: 0.875rem;
+ width: 100%;
+ margin-top: var(--header-height);
+ max-width: 1200px;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+table thead th {
+ position: sticky;
+ top: var(--header-height);
+ border-top: 1px solid var(--color-border);
+ border-bottom: 1px solid var(--color-border);
+ font-size: 0.75rem;
+ padding: 0.75rem 0.75rem calc(0.75rem - 2px);
+ line-height: 1;
+ font-weight: 400;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ color: var(--color-text-secondary);
+ backdrop-filter: blur(6px);
+ background-color: var(--color-background);
+ z-index: 10;
+}
+th.sortable {
+ cursor: pointer;
+ user-select: none;
+}
+
+.sort-indicator {
+ display: inline-block;
+ width: 1rem;
+ text-align: center;
+}
+
+th,
+td {
+ padding: 0.75rem;
+ text-align: left;
+ border-bottom: 1px solid var(--color-border);
+}
+
+tbody tr {
+ cursor: pointer;
+ transition: background-color 0.15s ease;
+
+ &:hover {
+ background-color: var(--color-surface);
+ }
+
+ td {
+ color: var(--color-text-tertiary);
+ }
+
+ .mode-name {
+ font-weight: 600;
+ color: var(--color-text);
+ }
+
+ .description {
+ max-width: 300px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ .votes {
+ font-weight: 500;
+ color: var(--color-text);
+ font-family: var(--font-mono);
+ }
+}
+
+dialog::backdrop {
+ backdrop-filter: blur(8px);
+ background-color: rgba(0, 0, 0, 0.1);
+}
+
+dialog {
+ margin: auto;
+ background-color: var(--color-background);
+ color: var(--color-text);
+ border: none;
+ border-radius: 0.5rem;
+ width: calc(100vw - 2rem);
+ max-width: 50rem;
+ max-height: calc(100svh - 2rem);
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05), 0 4px 8px rgba(0, 0, 0, 0.05),
+ 0 8px 16px rgba(0, 0, 0, 0.07), 0 16px 32px rgba(0, 0, 0, 0.07),
+ 0 32px 64px rgba(0, 0, 0, 0.07), 0 48px 96px rgba(0, 0, 0, 0.07);
+
+ flex-direction: column;
+ overflow: hidden;
+
+ &[open] {
+ display: flex;
+ }
+
+ .header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0.875rem 1rem calc(0.875rem - 4px) 1rem;
+ border-bottom: 1px solid var(--color-border);
+ flex: 0 0 auto;
+
+ .header-left {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 0.25rem;
+
+ .title-section {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+
+ h2 {
+ font-size: 1rem;
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: -0.5px;
+ line-height: 1;
+ margin: 0;
+ }
+
+ .vote-section {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 0.375rem;
+ flex: 1;
+
+ .vote-group,
+ .download-group {
+ display: flex;
+ align-items: center;
+ gap: 0.375rem;
+ }
+
+ /* Direct children - vote group on left, download on right */
+
+ .vote-btn,
+ .download-btn {
+ background: none;
+ border: 1px solid var(--color-border);
+ color: var(--color-text-secondary);
+ padding: 0.375rem;
+ border-radius: 0.25rem;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ &:hover {
+ border-color: var(--color-brand);
+ color: var(--color-brand);
+ background-color: rgba(253, 149, 39, 0.05);
+ }
+
+ &.voted,
+ &.downloaded {
+ border-color: var(--color-brand);
+ background-color: var(--color-brand);
+ color: var(--color-text-invert);
+ }
+
+ &.disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+ border-color: var(--color-border);
+ background-color: transparent;
+ color: var(--color-text-secondary);
+ }
+
+ &.disabled:hover {
+ border-color: var(--color-border);
+ color: var(--color-text-secondary);
+ background-color: transparent;
+ }
+ }
+
+ .vote-count,
+ .download-count {
+ font-weight: 600;
+ font-size: 0.875rem;
+ color: var(--color-text);
+ font-family: var(--font-mono);
+ min-width: 2ch;
+ text-align: center;
+ }
+ }
+ }
+
+ .author {
+ color: var(--color-text-secondary);
+ margin: 0;
+ font-style: italic;
+ font-size: 0.8125rem;
+ line-height: 1.2;
+ }
+ }
+ }
+
+ .mode-header {
+ padding: 1.5rem 1.5rem 0;
+ border-bottom: 1px solid var(--color-border);
+
+ .mode-header-info .description {
+ margin: 0 0 1.5rem;
+ line-height: 1.5;
+ color: var(--color-text);
+ font-size: 0.95rem;
+ }
+ }
+
+ .body {
+ padding: 1.5rem;
+ overflow-y: auto;
+ flex: 1 1 auto;
+ overscroll-behavior: contain;
+ font-size: 0.875rem;
+
+ /* Custom scrollbar styling */
+ scrollbar-width: thin;
+ scrollbar-color: var(--color-border) transparent;
+
+ &::-webkit-scrollbar {
+ width: 8px;
+ }
+
+ &::-webkit-scrollbar-track {
+ background: transparent;
+ }
+
+ &::-webkit-scrollbar-thumb {
+ background: var(--color-border);
+ border-radius: 4px;
+ border: 1px solid transparent;
+ background-clip: padding-box;
+ }
+
+ &::-webkit-scrollbar-thumb:hover {
+ background: var(--color-text-tertiary);
+ background-clip: padding-box;
+ }
+
+ .mode-content {
+ display: flex;
+ flex-direction: column;
+ gap: 1.5rem;
+
+ h4 {
+ font-size: 0.875rem;
+ font-weight: 600;
+ margin-bottom: 0.75rem;
+ color: var(--color-text-secondary);
+ }
+
+ .description {
+ color: var(--color-text-secondary);
+ line-height: 1.5;
+ font-size: 0.875rem;
+ }
+
+ .tools-list {
+ display: flex;
+ gap: 0.5rem;
+ flex-wrap: wrap;
+ }
+
+ .tool-tag {
+ background-color: var(--color-surface);
+ color: var(--color-text-secondary);
+ padding: 0.5rem 0.75rem;
+ border-radius: 0.25rem;
+ font-size: 0.8125rem;
+ font-family: var(--font-mono);
+ position: relative;
+ text-decoration: none;
+ display: inline-block;
+ transition: all 0.2s ease;
+
+ &.tool-enabled::after {
+ content: '';
+ position: absolute;
+ top: 0.25rem;
+ right: 0.25rem;
+ width: 6px;
+ height: 6px;
+ background-color: var(--color-brand);
+ border-radius: 50%;
+ border: 1px solid var(--color-background);
+ }
+
+ &.tool-disabled::after {
+ content: '';
+ position: absolute;
+ top: 0.25rem;
+ right: 0.25rem;
+ width: 6px;
+ height: 6px;
+ background-color: rgba(239, 68, 68, 0.3);
+ border-radius: 50%;
+ border: 1px solid var(--color-background);
+ }
+
+ &:hover {
+ background-color: var(--color-brand);
+ color: var(--color-text-invert);
+ transform: translateY(-1px);
+ }
+
+ &.tool-disabled:hover {
+ background-color: rgba(253, 149, 39, 0.3);
+ color: var(--color-text-secondary);
+ transform: translateY(-1px);
+ }
+ }
+
+ a.tool-tag {
+ cursor: pointer;
+ }
+ }
+
+ .context-instruction {
+ background-color: var(--color-background-accent);
+ color: var(--color-text-secondary);
+ padding: 0.5rem;
+ border-radius: 0 0.375rem 0.375rem 0;
+ border-left: 3px solid var(--color-brand);
+ position: relative;
+ overflow: hidden;
+
+ .copy-badge {
+ position: absolute;
+ top: 0;
+ right: 0;
+ margin: 0;
+ font-size: 0.65rem;
+ font-weight: 600;
+ color: var(--color-text-secondary);
+ background-color: var(--color-background);
+ padding: 0.3rem 0.5rem 0.2rem 0.5rem;
+ border-radius: 0 0.375rem 0 0;
+ border: 1px solid var(--color-border);
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ display: flex;
+ align-items: center;
+ line-height: 1;
+ cursor: pointer;
+ transition: background 0.2s, color 0.2s;
+ z-index: 2;
+ }
+ .copy-badge:active,
+ .copy-badge:focus {
+ background-color: var(--color-brand);
+ color: var(--color-text-invert);
+ outline: none;
+ }
+ .copy-badge.copied {
+ background-color: var(--color-brand);
+ color: var(--color-text-invert);
+ }
+
+ h5 {
+ position: absolute;
+ top: 0;
+ right: 0;
+ margin: 0;
+ font-size: 0.65rem;
+ font-weight: 600;
+ color: var(--color-text-secondary);
+ background-color: var(--color-background);
+ padding: 0.3rem 0.5rem 0.2rem 0.5rem;
+ border-radius: 0 0.375rem 0 0;
+ border: 1px solid var(--color-border);
+
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ display: flex;
+ align-items: center;
+ line-height: 1;
+ }
+
+ pre {
+ margin: 0;
+ overflow-x: auto;
+ max-width: 100%;
+
+ code {
+ display: block;
+ font-size: 0.875rem;
+ padding: 6px;
+ padding-left: 10px;
+ padding-right: 2rem;
+ line-height: 1.5;
+ white-space: pre-wrap;
+ color: var(--color-text-secondary);
+
+ border-radius: 0.25rem;
+ overflow-wrap: break-word;
+ }
+ }
+
+ .context-content {
+ font-size: 0.875rem;
+ padding: 6px;
+ padding-left: 10px;
+ padding-right: 2rem;
+ line-height: 1.5;
+ white-space: pre-wrap;
+ color: var(--color-text-secondary);
+ overflow-x: auto;
+ word-wrap: break-word;
+ }
+ }
+
+ .context-instructions {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+ }
+
+ h2,
+ p,
+ .code-block {
+ margin-bottom: 0.625rem;
+
+ &:has(+ h2) {
+ margin-bottom: 1.5rem;
+ }
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
+
+ h2 {
+ font-size: 1rem;
+ font-weight: 500;
+ }
+
+ p {
+ b {
+ font-weight: 500;
+ }
+ }
+
+ .code-block {
+ padding: 0.875rem 1rem;
+ border-radius: 0.25rem;
+ background-color: var(--color-surface);
+ }
+
+ code {
+ font-size: 0.8125rem;
+ font-family: var(--font-mono);
+ }
+ }
+
+ .footer {
+ flex: 0 0 auto;
+ text-align: center;
+ border-top: 1px solid var(--color-border);
+ padding: 0.875rem 1rem 0.875rem;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+
+ a {
+ font-size: 0.75rem;
+ color: var(--color-text-tertiary);
+ text-decoration: none;
+
+ &:hover,
+ &:visited {
+ color: var(--color-text-tertiary);
+ }
+ }
+ }
+}
diff --git a/packages/openmodes/src/index.ts b/packages/openmodes/src/index.ts
new file mode 100644
index 00000000..6f91a724
--- /dev/null
+++ b/packages/openmodes/src/index.ts
@@ -0,0 +1,621 @@
+// Escape HTML for safe rendering of XML/markdown tags
+function escapeHtml(str: string): string {
+ return str
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''');
+}
+
+// Convert mode id to display name (title case)
+function titleCase(str: string): string {
+ return str
+ .split('-')
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
+ .join(' ');
+}
+
+class DOMElements {
+ static modeModal = document.getElementById('mode-modal') as HTMLDialogElement;
+ static helpModal = document.getElementById('help-modal') as HTMLDialogElement;
+ static closeHelpBtn = document.getElementById('close-help')!;
+ static helpBtn = document.getElementById('help')!;
+ static search = document.getElementById('search')! as HTMLInputElement;
+ static upvoteBtn = document.getElementById('upvote-btn')!;
+ static downvoteBtn = document.getElementById('downvote-btn')!;
+ static downloadBtn = document.getElementById('download-btn')!;
+ static voteCountEl = document.getElementById('modal-votes')!;
+ static downloadCountEl = document.getElementById('modal-downloads')!;
+}
+
+let currentMode: any = null;
+
+class LocalStorage {
+ static getJSON(key: string, defaultValue: T): T {
+ try {
+ const data = localStorage.getItem(key);
+ return data ? JSON.parse(data) : defaultValue;
+ } catch {
+ return defaultValue;
+ }
+ }
+
+ static setJSON(key: string, value: any): void {
+ try {
+ localStorage.setItem(key, JSON.stringify(value));
+ } catch (error) {
+ console.error('Failed to save to localStorage:', error);
+ }
+ }
+}
+
+class UserDataManager {
+ static getDownloadStatus(modeId: string): boolean {
+ const downloads = LocalStorage.getJSON(
+ 'openmodes-downloads',
+ {} as Record
+ );
+ return downloads[modeId] || false;
+ }
+
+ static setDownloadStatus(modeId: string) {
+ const downloads = LocalStorage.getJSON(
+ 'openmodes-downloads',
+ {} as Record
+ );
+ downloads[modeId] = true;
+ LocalStorage.setJSON('openmodes-downloads', downloads);
+ }
+
+ static getVoteStatus(modeId: string): 'up' | 'down' | null {
+ const votes = LocalStorage.getJSON(
+ 'openmodes-votes',
+ {} as Record
+ );
+ return votes[modeId] || null;
+ }
+
+ static setVoteStatus(modeId: string, vote: 'up' | 'down' | null) {
+ const votes = LocalStorage.getJSON(
+ 'openmodes-votes',
+ {} as Record
+ );
+ if (vote === null) {
+ delete votes[modeId];
+ } else {
+ votes[modeId] = vote;
+ }
+ LocalStorage.setJSON('openmodes-votes', votes);
+ }
+}
+
+class URLManager {
+ static getQueryParams() {
+ return new URLSearchParams(window.location.search);
+ }
+
+ static updateQueryParams(updates: Record) {
+ const params = URLManager.getQueryParams();
+ for (const [key, value] of Object.entries(updates)) {
+ if (value) {
+ params.set(key, value);
+ } else {
+ params.delete(key);
+ }
+ }
+ const newPath = params.toString()
+ ? `${window.location.pathname}?${params.toString()}`
+ : window.location.pathname;
+ window.history.pushState({}, '', newPath);
+ }
+
+ static getColumnNameForURL(headerEl: Element): string {
+ const text = headerEl.textContent?.trim().toLowerCase() || '';
+ return text.replace(/↑|↓/g, '').trim().split(/\s+/).slice(0, 2).join('-');
+ }
+
+ static getColumnIndexByUrlName(name: string): number {
+ const headers = document.querySelectorAll('th.sortable');
+ return Array.from(headers).findIndex(
+ (header) => URLManager.getColumnNameForURL(header) === name
+ );
+ }
+}
+
+class ModalManager {
+ static helpModalScrollY = 0;
+
+ static openHelp() {
+ ModalManager.helpModalScrollY = window.scrollY;
+ document.body.style.position = 'fixed';
+ document.body.style.top = `-${ModalManager.helpModalScrollY}px`;
+ DOMElements.helpModal.showModal();
+ }
+
+ static closeHelp() {
+ DOMElements.helpModal.close();
+ document.body.style.position = '';
+ document.body.style.top = '';
+ window.scrollTo(0, ModalManager.helpModalScrollY);
+ }
+
+ static closeMode() {
+ DOMElements.modeModal.close();
+ document.body.style.position = '';
+ document.body.style.top = '';
+ window.scrollTo(0, ModalManager.helpModalScrollY);
+ currentMode = null;
+ }
+}
+
+class TableManager {
+ static currentSort = { column: -1, direction: 'asc' as 'asc' | 'desc' };
+
+ static sort(column: number, direction: 'asc' | 'desc') {
+ const header = document.querySelectorAll('th.sortable')[column];
+ const columnType = header.getAttribute('data-type');
+ if (!columnType) return;
+
+ TableManager.currentSort = { column, direction };
+ URLManager.updateQueryParams({
+ sort: URLManager.getColumnNameForURL(header),
+ order: direction
+ });
+
+ const tbody = document.querySelector('table tbody')!;
+ const rows = Array.from(
+ tbody.querySelectorAll('tr')
+ ) as HTMLTableRowElement[];
+
+ rows.sort((a, b) => {
+ const aValue = TableManager.getCellValue(a.cells[column], columnType);
+ const bValue = TableManager.getCellValue(b.cells[column], columnType);
+
+ if (aValue === undefined && bValue === undefined) return 0;
+ if (aValue === undefined) return 1;
+ if (bValue === undefined) return -1;
+
+ let comparison = 0;
+ if (columnType === 'number' || columnType === 'tools') {
+ comparison = (aValue as number) - (bValue as number);
+ } else {
+ comparison = (aValue as string).localeCompare(bValue as string);
+ }
+
+ return direction === 'asc' ? comparison : -comparison;
+ });
+
+ rows.forEach((row) => tbody.appendChild(row));
+ TableManager.updateSortIndicators(column, direction);
+ }
+
+ static getCellValue(
+ cell: HTMLTableCellElement,
+ type: string
+ ): string | number | undefined {
+ const text = cell.textContent?.trim() || '';
+ if (text === '-') return;
+ if (type === 'number') return parseFloat(text.replace(/[$,]/g, '')) || 0;
+ return text;
+ }
+
+ static updateSortIndicators(activeColumn: number, direction: 'asc' | 'desc') {
+ const headers = document.querySelectorAll('th.sortable');
+ headers.forEach((header, i) => {
+ const indicator = header.querySelector('.sort-indicator')!;
+ indicator.textContent =
+ i === activeColumn ? (direction === 'asc' ? '↑' : '↓') : '';
+ });
+ }
+
+ static filter(value: string) {
+ const lowerCaseValue = value.toLowerCase();
+ const rows = document.querySelectorAll(
+ 'table tbody tr'
+ ) as NodeListOf;
+
+ rows.forEach((row) => {
+ const cellTexts = Array.from(row.cells).map((cell) =>
+ cell.textContent!.toLowerCase()
+ );
+ const isVisible = cellTexts.some((text) => text.includes(lowerCaseValue));
+ row.style.display = isVisible ? '' : 'none';
+ });
+
+ URLManager.updateQueryParams({ search: value || null });
+ }
+}
+
+async function openModeModal(row: HTMLTableRowElement) {
+ const modeId = row.getAttribute('data-mode-id');
+ if (!modeId) return;
+
+ try {
+ const response = await fetch(`/mode/${modeId}`);
+ const mode = await response.json();
+ currentMode = mode;
+
+ populateModalContent(mode);
+ updateVoteButtons(modeId);
+ updateDownloadButton(modeId);
+
+ ModalManager.helpModalScrollY = window.scrollY;
+ document.body.style.position = 'fixed';
+ document.body.style.top = `-${ModalManager.helpModalScrollY}px`;
+ DOMElements.modeModal.showModal();
+ } catch (error) {
+ console.error('Failed to load mode data:', error);
+ }
+}
+
+function populateModalContent(mode: any) {
+ const modalElements = {
+ title: document.getElementById('modal-title')!,
+ author: document.getElementById('modal-author')!,
+ description: document.getElementById('modal-description')!,
+ systemPrompt: document.getElementById('modal-system-prompt')!
+ };
+
+ modalElements.title.textContent = titleCase(mode.id);
+ modalElements.author.textContent = mode.author;
+ modalElements.description.textContent = mode.description;
+ DOMElements.voteCountEl.textContent = mode.votes.toString();
+ DOMElements.downloadCountEl.textContent = mode.downloads.toString();
+
+ modalElements.systemPrompt.innerHTML = `