# Myco Integration

Myco is an open discovery network for websites. Adding Myco integration means: a manifest file, a script tag, and data attributes on content elements.

> **Agents:** this is the full single-file guide. For a lighter read, start at
> [https://myco.stream/llms.txt](https://myco.stream/llms.txt) — an index that
> routes you to only the docs your site's content types need.

**Read the TypeScript schema first:** [https://myco.stream/sdk/types.ts](https://myco.stream/sdk/types.ts)

That file defines every field, type, and constraint. This document tells you what to do with it.

---

## Step 1: Understand the Rules

Before writing any code, internalize these:

- **A node declares only its own content.** A band site declares its own music. A business site declares its own listing. Nobody declares content on behalf of someone else. If a site lists other businesses, those listings do NOT go in the manifest — only the site's own business identity does.
- **Business is singular.** The `business` field in the manifest is a single object, not an array. The node IS the business. If another business wants to be on Myco, it needs its own website with its own manifest.
- **The domain is the identity.** No registration required. If `/.well-known/myco.json` exists on a custom domain, the Myco crawler will find and index it.
- **Custom domains only.** Hosting provider subdomains are not indexed: `*.pages.dev`, `*.netlify.app`, `*.vercel.app`, `*.github.io`, `*.workers.dev`, or any other provider subdomain. The site must be on a custom domain.
- **Subdomains roll up to the root domain.** One listing per root domain. Pages on `artist1.media.com` and `artist2.media.com` all credit the `media.com` listing — analytics, content, comments, and moderation attach to the root. The manifest must live at the ROOT domain: `https://media.com/.well-known/myco.json`.
- **All paths are relative.** Every file path and URL in the manifest must be relative from the site root (e.g., `/audio/track.mp3`, `/images/cover.webp`). No absolute URLs. No external URLs. No `https://` prefixes on content paths. The hub adds the domain when constructing full URLs. The one exception is `business.contact.website` which must be an absolute URL matching `node.url`.
- **No assumptions about user data.** You do not know the site owner's business name, phone number, address, bio, or any other personal/business information. ASK THE USER for every piece of information you cannot read directly from the existing site. Do not fabricate, guess, or use placeholder values for real-world data.
- **Plain text in text fields.** Excerpts, descriptions, bios, and captions must be plain text. No markdown (`#`, `**`, `[]()`, `![]()`), no HTML tags. Strip all formatting before inserting into the manifest.

---

## Step 2: Gather Information

**Before generating the manifest, ask the user for anything you don't already know.** This includes but is not limited to:

- Site name and one-line bio/description
- Physical location (if applicable — business, studio, etc.)
- Contact info (phone, email) for business listings
- Business hours (if applicable)
- Services offered (if applicable)
- Social media links
- Whether AI tools were used in creating the site's content, and if so, which tools and what the human's role was
- What content types exist on the site (music, articles, photography, etc.)

Do not proceed with manifest generation until you have real answers. Placeholder text like "Your Business Name" or "555-555-5555" in a shipped manifest is unacceptable.

If the site's content already contains this information (in existing HTML, config files, or a database), read it from there. Only ask the user for what you can't find.

---

> [!IMPORTANT]
> **Strict Halt Condition**: After completing your research but **BEFORE** writing `myco.config.json` or any manifest scripts, you **MUST** provide a "Discovery Report" to the user and wait for their confirmation.
>
> Your report must include:
> 1. **Primary Domain**: (e.g., `green.computer`) — verify if multiple domains are found in SEO tags, sitemaps, or emails.
> 2. **Business Metadata**: (e.g., Name, Phone, Address found in the code or to be filled by user).
> 3. **Content Inventory**: List all content types found (e.g., "Articles in `content/`", "Galleries in `data/projects.ts`", "Music", "Videos").
>
> **You are strictly prohibited** from making any code changes until the user responds with "CONFIRMED" or provides corrections. Treat any data found in the source code only as a "suggestion" to be verified. Do not proceed with manifest generation until you have real, user-confirmed answers.

---

## Step 3: Create the Manifest

Read the full schema: [https://myco.stream/sdk/types.ts](https://myco.stream/sdk/types.ts)

The manifest lives at `/.well-known/myco.json`. It must satisfy the `MycoManifest` type from that schema.

### For static sites (no database):

Create two files:

1. **`myco.config.json`** — The source of truth. Same schema as the manifest minus `updated_at`. This is the only file that gets manually updated when content changes.

2. **`generate-myco-manifest.mjs`** — Build script that reads the config, validates paths, writes the manifest:

```javascript
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';

const config = JSON.parse(readFileSync('myco.config.json', 'utf-8'));

function validatePaths(obj, prefix = '') {
  for (const [key, val] of Object.entries(obj)) {
    if (typeof val === 'string' && val.startsWith('/') && (
      val.endsWith('.mp3') || val.endsWith('.mp4') || val.endsWith('.webp') ||
      val.endsWith('.avif') || val.endsWith('.html') || val.endsWith('.vtt')
    )) {
      if (!existsSync('.' + val)) {
        console.warn(`⚠ Missing file: ${val} (referenced in ${prefix}${key})`);
      }
    }
    if (val && typeof val === 'object') validatePaths(val, `${prefix}${key}.`);
  }
}

validatePaths(config);

const manifest = { ...config, updated_at: new Date().toISOString() };
mkdirSync('.well-known', { recursive: true });
writeFileSync('.well-known/myco.json', JSON.stringify(manifest, null, 2));
console.log('✓ .well-known/myco.json generated');
console.warn('⚠️  MYCO: When adding or changing content, update myco.config.json FIRST. See MYCO.md');
```

Wire it into the build:

```json
{
  "scripts": {
    "build:manifest": "node generate-myco-manifest.mjs",
    "build": "npm run build:manifest && your-existing-build-command"
  }
}
```

Add `.well-known/myco.json` to `.gitignore` — it's a build artifact.

### For database-driven sites:

Create a dynamic route at `/.well-known/myco.json` that queries the database and returns the manifest JSON. Map your database fields to the TypeScript schema. The manifest stays in sync automatically.

Framework route paths:
- **Next.js App Router:** `app/.well-known/myco.json/route.ts`
- **SvelteKit:** `src/routes/.well-known/myco.json/+server.ts`
- **Nuxt:** `server/routes/.well-known/myco.json.ts`
- **Remix/TanStack Start:** handle in your server routes
- **Static hosts:** use the build script approach above

Verify the URL returns JSON after deploying. Many frameworks strip `.well-known/` — test it.

### Claim the site (don't skip)

Without a claim code the owner never sees their analytics dashboard. Ask the
owner to sign in at https://myco.stream and open their dashboard — it shows one
account-wide claim code. Add it inside the manifest's `node` block:

```json
"claim_code": "myco-verify-abc123"
```

It is the SAME code for every site they own. If they can't get it right now,
ship without it, but tell them the dashboard stays locked until the code is in
the manifest and the site is re-crawled.

### Content update rule

**Whenever content changes on the site, the manifest must update too.**

For static sites: update `myco.config.json` when adding, removing, or changing any content. The build script regenerates the manifest on deploy.

For database sites: the dynamic route reads from the database, so it stays in sync automatically. Just verify the route survives framework upgrades.

---

## Step 4: Add the Script Tag and Attribution

Place before `</body>` on every page:

```html
<script src="https://myco.stream/myco.js" async></script>
```

**Always load the script from `https://myco.stream/myco.js` — never download or
self-host it.** A vendored copy freezes the integration at one version and
silently misses new features and fixes (analytics, players, comments all evolve).
If the project contains a local `myco.js`, delete it and use the hosted URL.

Place the "Powered by Myco" badge in the site footer — exactly one badge, visible on every page:

```html
<a href="https://myco.stream" target="_blank" rel="noopener" data-myco-attribution>
  <img src="https://myco.stream/badge.svg" alt="Powered by Myco" width="124" height="40" style="border:0">
</a>
```

Badge rules:

- The hosted script upgrades hub-root links to a site invitation at `/ref/your-domain`. Add `data-myco-referral="off"` to keep a plain hub link. Custom destinations are preserved. Referral recognition requires a new site and verified ownership; signups alone do not earn credit.

- **Static HTML only.** The badge must appear in the server-rendered/static HTML — visible in view-source, not injected by client-side JavaScript. The live checker fetches the raw HTML; a JS-rendered footer fails the check. On client-rendered SPAs, put the badge in the static shell (`index.html`) or prerender the footer.
- **One badge, not two.** Do not add any other Myco attribution ("Connected via Myco", text links, etc.). If an older text attribution exists, replace it with the badge.
- **Hotlink `badge.svg`** from `https://myco.stream/badge.svg` — never download or recreate it. Design updates propagate automatically, same rule as `myco.js`.
- Scale with `height` if needed (keep the aspect ratio). Must stay visible and clickable — not hidden, not `display: none`, not zero-opacity.

---

## Step 5: Add Data Attributes

Add `data-myco-*` attributes to existing HTML elements so `myco.js` knows what to enhance. No attributes = no integration for that content.

### Music

On each track container:
```html
<div data-myco-track="SLUG" data-myco-src="/audio/SLUG.mp3"
     data-myco-title="Title" data-myco-artist="Artist"
     data-myco-duration="SECONDS" data-myco-release="RELEASE_SLUG">
  <button data-myco-play>Play</button>
  <span data-myco-track-title></span>
  <span data-myco-track-duration></span>
</div>
```

On release container:
```html
<div data-myco-release="SLUG">
  <span data-myco-play-count>—</span> plays
  <span data-myco-like-count>—</span> likes
</div>
```

### Video

```html
<div data-myco-video="SLUG" data-myco-src="/video/SLUG.mp4"
     data-myco-poster="/video/thumbs/SLUG.avif"
     data-myco-duration="SECONDS" data-myco-title="Title">
  <video data-myco-video-element></video>
  <span data-myco-view-count>—</span> views
</div>
```

### Articles

```html
<article data-myco-article="SLUG" data-myco-title="Title"
         data-myco-category="CAT" data-myco-read-time="MINUTES">
  <span data-myco-view-count>—</span> views
</article>
```

### Art

```html
<div data-myco-artwork="SLUG" data-myco-title="Title"
     data-myco-full-src="/images/SLUG-full.webp"
     data-myco-thumb-src="/images/thumbs/SLUG.avif">
  <img data-myco-artwork-trigger src="/images/thumbs/SLUG.avif" alt="Title">
</div>
```

### Photography

```html
<div data-myco-gallery="SLUG" data-myco-title="Title">
  <img data-myco-gallery-item="/photos/gallery/photo-001.webp"
       data-myco-caption="Caption text" alt="Description">
</div>
```

### Podcasts

On the show landing container:
```html
<div data-myco-show="SHOW_SLUG" data-myco-title="Show Title">
  <span data-myco-view-count>—</span> views
</div>
```

On each episode container (episodes play in the persistent player bar):
```html
<div data-myco-episode="EPISODE_SLUG" data-myco-src="/podcast/episodes/EPISODE_SLUG.mp3"
     data-myco-title="Episode Title" data-myco-author="Show or Host Name"
     data-myco-duration="SECONDS" data-myco-show="SHOW_SLUG">
  <button data-myco-play>Play</button>
  <span data-myco-play-count>—</span> plays
</div>
```

### Business

```html
<div data-myco-business="SLUG" data-myco-name="Name"
     data-myco-category="CAT" data-myco-phone="PHONE"
     data-myco-email="EMAIL">
  <a data-myco-contact-phone href="tel:PHONE">Call</a>
  <a data-myco-contact-email href="mailto:EMAIL">Email</a>
  <span data-myco-review-count>—</span> reviews
</div>
```

### Injection points

Place these empty elements where `myco.js` should inject UI components:

```html
<div data-myco-social></div>       <!-- like/bookmark/share buttons -->
<div data-myco-comments></div>     <!-- full comment thread + composer (works on every content type) -->
<div data-myco-supporters></div>   <!-- follower avatar row for the site -->
<div data-myco-player></div>       <!-- persistent music/podcast player bar -->
```

Place `data-myco-social` and `data-myco-comments` **inside** a content container (a `data-myco-track`, `data-myco-video`, `data-myco-article`, etc. element) — that's how `myco.js` knows which content they belong to. `data-myco-comments` gives every page a fully hosted comment system (sign-in, storage, and moderation included) — think Disqus with zero setup. `data-myco-supporters` and `data-myco-player` are site-level and can go anywhere.

These must be empty. `myco.js` owns their content.

### Counter placeholders

Use `—` (em dash) as the placeholder text in counter spans. Not `0`. `myco.js` overwrites them with live numbers once connected.

---

## Asset Standards

### Images
- **Thumbnails:** AVIF, 400×400px square crop, quality 60-70
- **Full-size:** WebP, max 2400px long edge
- **Alt text:** Required on every `<img>`. Use the content title or a description.

### Audio
- **Streams:** MP3, 320kbps CBR
- **Previews:** 30 seconds from the most representative section (not the intro)

### Video thumbnails
- Discovery card: AVIF, 400×400px square crop
- Player poster: WebP, 640×360px (16:9)

### Filenames
Filenames MUST match their manifest `id`:
```
Track id: "neon-meridian"     → /audio/neon-meridian.mp3
Artwork id: "phobots-in-rain" → /images/phobots-in-rain-full.webp
```
No random hashes, no camera filenames, no UUIDs.

### Dominant Color
Include `dominant_color` hex for any content with artwork or thumbnails. The hub uses it as a loading placeholder. Compute from the image at build time (most common pixel color or weighted center average).

### OG Meta Tags
Required on every page, values matching the manifest:
```html
<meta property="og:title" content="TITLE">
<meta property="og:description" content="DESCRIPTION">
<meta property="og:image" content="https://DOMAIN/path/to/cover.webp">
<meta property="og:url" content="https://DOMAIN/PATH">
<meta property="og:site_name" content="SITE NAME">
```

---

## What myco.js Does

Once the script tag and data attributes are in place, `myco.js` handles everything else automatically:

**Tracks automatically** (no extra code needed):
- Page views and referrers for analytics
- Music and podcast play events (start, duration, completion)
- Video watch events
- Article time-on-page and scroll depth
- Art/photo lightbox opens
- Podcast show views
- Business contact link clicks (phone, email, website)

**Writes live data** to counter elements:
- `data-myco-play-count` — total plays for a release, track, or podcast episode
- `data-myco-like-count` — likes for any content
- `data-myco-view-count` — views for video/article/artwork/gallery/business/podcast show
- `data-myco-review-count` — reviews for a business
- `data-myco-comment-count` — comments for any content

**Injects UI** into injection point divs:
- `data-myco-social` — like, bookmark, share buttons
- `data-myco-comments` — hosted comment thread with sign-in (any content type)
- `data-myco-supporters` — follower avatars for the site
- `data-myco-player` — persistent music/podcast player bar

The site owner sees all stats on their Myco dashboard at myco.stream.

---

## Step 6: Plant Seeds for Future Agents

AI agents don't have memory between sessions. If a future agent adds a blog post without updating the manifest, the Myco integration breaks silently.

The most effective seed is already in the build script: `console.warn` in `generate-myco-manifest.mjs` outputs a warning every time the project builds. Agents always read build output. They're trained to investigate warnings. This is the primary tripwire.

**Additional seeds — place in files agents commonly open:**

**README.md** — near the top:
```markdown
> ⚠️ This site is connected to [Myco](https://myco.stream). See `MYCO.md` before adding or changing content.
```

**package.json** — in scripts (ignored by npm, visible to agents reading the file):
```json
"//myco": "⚠️ See MYCO.md before adding or changing content"
```

**Database route file** (if applicable) — at the top:
```typescript
// ⚠️ MYCO: This route generates the manifest. If you change the content schema, update this too. See MYCO.md
```

---

## Step 7: Add MYCO.md

Download the integration spec template into the project root, then fill it in
for THIS site:

```
curl -o MYCO.md https://myco.stream/sdk/MYCO.md
```

`MYCO.md` is this site's Myco integration spec — like `CLAUDE.md` is for Claude.
It is what the build warning and all seeds point to. Fill in the "Site identity"
section (domain, node name/bio, content types, manifest location, ownership
`claim_code`, and the `data-myco-*` attributes in use and where). From now on,
any agent that changes this site must READ `MYCO.md` first and UPDATE it — and
keep `myco.config.json` / `.well-known/myco.json` in sync — after every change,
so the integration survives structural changes to the site.

---

## Verify Live (after deploy)

Don't stop at the static checklist — prove the integration from outside:

- **Site checker (send this link to the owner):**
  `https://myco.stream/check?domain=example.com` — live pass/fail on script tag,
  manifest, schema, attribution, and network registration.
- **API (for agents):** `curl "https://api.myco.stream/api/validate?domain=example.com"`
  returns the same checks as JSON: `{ ok, summary, checks: [{ id, label, status: pass|fail|warn, detail }] }`.
  Fix every `fail`, then re-run.
- **Browser console:** myco.js logs a collapsed "integration report" group on
  every page — content containers found, counters wired vs orphaned, injection
  points, auth state. Orphaned counters mean a slug mismatch with the manifest.

A "Registered in Myco network" warning just means not crawled yet — visits with
the embed installed trigger the first crawl.

---

## Validation Checklist

Before considering integration complete:

- [ ] `myco.config.json` exists and is valid JSON (static) OR dynamic route returns valid JSON
- [ ] `/.well-known/myco.json` is accessible at the deployed URL
- [ ] `node.url` matches the actual custom domain
- [ ] `node.content_types` lists every section in the manifest
- [ ] Every `id` is lowercase, hyphens only, unique
- [ ] All file paths are relative (start with `/`, no `https://`)
- [ ] No external URLs in the manifest
- [ ] No placeholder data — all names, phone numbers, descriptions are real
- [ ] `business` is a single object (not an array) if present
- [ ] `myco.js` script tag present before `</body>`, loaded from `https://myco.stream/myco.js` (not a local copy)
- [ ] "Powered by Myco" badge (`https://myco.stream/badge.svg`) in the footer, in static/server-rendered HTML — exactly one
- [ ] Counter elements use `—` placeholder, not `0` or hardcoded numbers
- [ ] Injection point divs are empty
- [ ] `.well-known/myco.json` is in `.gitignore` (static sites)
- [ ] Build command generates the manifest (static sites)
- [ ] OG meta tags present on every page
- [ ] All images have alt text
- [ ] `MYCO.md` exists in project root
- [ ] README.md has the `⚠️` Myco reminder
- [ ] Build output shows the `⚠️ MYCO` warning
- [ ] `package.json` has the `//myco` comment entry
- [ ] `node.claim_code` set from the owner's dashboard (or owner told analytics stay locked without it)
- [ ] Live check passes: https://myco.stream/check?domain=YOURDOMAIN (agents: GET api.myco.stream/api/validate)
