AGENTS.md · diff

git:20260816.3161ba8 to git:20260830.445ff5b

97 added, 9 removed. Audit B to B.

# Marketing Agents
> [!IMPORTANT]
> **For CLI runtime usage**, read `CONTEXT.md`.
> **For /cmo runtime indexes**, read `skills/cmo/rules/cli-runtime-index.md`,
> `skills/cmo/rules/publish-index.md`, and
> `skills/cmo/rules/studio-api-index.md`.
> **For /axi (AXI catalog router — gh-axi, chrome-devtools-axi, principles)**, read `skills/axi/SKILL.md`.
> **For development standards**, read `CLAUDE.md`.
- > This file covers the 5 marketing sub-agents and the drop-in contracts for skills and agents.
+ > This file covers the repository landscape, local runtime, marketing sub-agents, and drop-in contracts.
+ ## Repository Landscape
+
+ `marketing-cli` is the public product source of truth. It owns the CLI, `/cmo`, skills, agents,
+ canonical manifests, publishing adapters, and the shipping Studio app under `studio/`.
+
+ | Repository | Role | Relationship to `marketing-cli` |
+ |---|---|---|
+ | `MoizIbnYousaf/marketing-cli` (this repo) | Canonical CLI package and bundled Studio | Make product, skill, agent, catalog, and Studio changes here. The npm tarball includes `studio/`. |
+ | `MoizIbnYousaf/mktg-site` | Public `marketing-cli.com` website | Its `website/` app presents the product. Its root `skills-manifest.json` is a timestamped downstream mirror, not an authoring source. |
+ | `MoizIbnYousaf/Ai-Agent-Skills` | Public skill registry and optional installer | Its `skills.json` points to live sources in this repo; it does not own or vendor the skills. Runtime delegation is opt-in via `MKTG_USE_AI_AGENT_SKILLS=1`. |
+ | `MoizIbnYousaf/mktg-studio` | Legacy standalone Studio checkout | Compatibility/dev fallback only. The current product app is this repo's `studio/`; do not implement Studio features in the standalone repo unless explicitly maintaining that fallback. |
+
+ ### Source-of-truth flows
+
+ ```text
+ skills/* + skills-manifest.json (marketing-cli, canonical)
+ │
+ └─ maintainer propagation
+ ├─> mktg-site/skills-manifest.json (timestamps/activity)
+ └─> Ai-Agent-Skills/skills.json (curated registry metadata)
+
+ marketing-cli/src + skills + agents + studio
+ └─> npm package `marketing-cli`
+
+ marketing-cli/studio ──reads/writes──> user project root
+ ├─ brand/
+ ├─ marketing/
+ └─ .mktg/ + SQLite state
+ ```
+
+ - Edit a skill and the canonical manifest here first. Do not hand-edit downstream mirror fields to
+ simulate propagation. Downstream publication is a distinct maintainer action after the canonical
+ commit.
+ - `mktg-site/website` and this repo's `studio/` are different web apps: the former is the public
+ marketing site, while the latter is the local authenticated product UI for a user's project.
+ - Studio and CLI must use the same project root so `brand/`, `marketing/`, and `.mktg/` agree. Set
+ `MKTG_PROJECT_ROOT` when the target project is not the checkout root.
+ - External catalogs are runtime boundaries, not sibling product repos: Postiz
+ (`gitroomhq/postiz-app`) is called over raw HTTP; OpenSEO (`every-app/open-seo`) is HTTP/MCP.
+ Upstream-mirrored skills additionally track provenance in their own `upstream.json` files.
+
+ ### External upstreams
+
+ These repositories contribute behavior or mirrored methodology but do not own the product:
+
+ | Upstream | Boundary |
+ |---|---|
+ | `gitroomhq/postiz-app` | Postiz publishing service, accessed through the raw HTTP adapter declared in `catalogs-manifest.json`. |
+ | `every-app/open-seo` | OpenSEO research service and skill family, accessed over HTTP/MCP. |
+ | `exa-labs/agent-skills` | Provenance source for Exa-backed research skills; Exa remains a BYO integration. |
+ | `higgsfield-ai/skills` | Provenance source for Higgsfield generation skills. |
+ | `kunchenguid/axi` | Source for the AXI tool-interface catalog/router methodology. |
+ | `remotion-dev/remotion` and `remotion-dev/skills` | Provenance sources for the Remotion mirrored skill; update only through its `upstream.json` + upgrade workflow. |
+
+ ### Finding sibling repositories
+
+ The conventional layout is `.../mktgmono/{marketing-cli,mktg-site,mktg-studio}`.
+ Amp orbs may instead mount this checkout at `$PWD` and connected repos under `../repos/`.
+ Resolve paths in this order rather than assuming one machine-specific root:
+
+ 1. Explicit override: `MKTG_CLI_PATH`, `MKTG_SITE_PATH`, or `AI_AGENT_SKILLS_PATH`.
+ 2. Conventional sibling next to this checkout.
+ 3. Orb connected-repo path under `../repos/`.
+
+ `MKTG_STUDIO_BIN` may override Studio launcher resolution for compatibility testing, but normal
+ development and releases must use the bundled `studio/` app.
+
+ ### End-to-end local workflow
+
+ ```bash
+ # CLI + bundled Studio (from marketing-cli)
+ bun install --frozen-lockfile
+ bun run build
+ bun run dev -- doctor --json
+ MKTG_PROJECT_ROOT="$PWD" bun run studio/bin/mktg-studio.ts --no-open
+
+ # Public website (from mktg-site/website)
+ bun install --frozen-lockfile
+ bun run dev
+ ```
+
+ In an Amp orb, prefer `amp orb services ensure`: `.amp/services.yaml` supervises and portals both
+ the bundled Studio and the connected `mktg-site` checkout. The CLI itself is terminal-only; Studio
+ is its browser-facing app.
+
6 sub-agents that `/cmo` spawns for parallel research and review. Installed to `~/.claude/agents/` by `mktg init` and `mktg update`.
## Agent Registry
| Agent | Category | Writes | Reads skill | Spawned by |
|---|---|---|---|---|
| `mktg-brand-researcher` | research | `brand/voice-profile.md` | `brand-voice` | `/cmo` foundation |
| `mktg-audience-researcher` | research | `brand/audience.md` | `audience-research` | `/cmo` foundation |
| `mktg-competitive-scanner` | research | `brand/competitors.md` | `competitive-intel` | `/cmo` foundation |
| `mktg-content-reviewer` | review | none (scores only) | reads `brand/voice-profile.md` | on demand |
| `mktg-seo-analyst` | review | none (scores only) | reads `brand/keyword-plan.md` | on demand |
## How Agents Work
1. **Registry:** `agents-manifest.json` is the source of truth for all agent metadata.
2. **Installation:** `mktg init` and `mktg update` copy agent `.md` files from the package to `~/.claude/agents/`.
3. **Spawning:** `/cmo` spawns all 3 research agents in a single message for maximum parallelism.
4. **Autonomy:** Agents do not ask questions. They research, analyze, and write.
5. **Isolation:** Agents never call other agents. `/cmo` orchestrates.
## Security Posture
> [!IMPORTANT]
> **The agent is not a trusted operator.** Assume all inputs can be adversarial.
| Rule | Detail |
|---|---|
| File writes | Constrained to `brand/`, `marketing/`, `.mktg/` via `sandboxPath()` |
| Resource IDs | Reject `..`, absolute paths, control characters |
| CLI output | Use `--json` for all calls. TTY output is for humans, JSON is the agent contract. |
| Mutations | Use `--dry-run` before any mutating operation |
| Context window | Use `--fields` to minimize payload size |
## Drop-in Skill Contract
Add a new marketing skill without touching CLI code.
### Steps
1. Create `skills/<skill-name>/SKILL.md`
2. Add an entry to `skills-manifest.json`
3. Run `mktg update` to install
### SKILL.md Requirements
| Requirement | Detail |
|---|---|
| Max length | 500 lines. Offload depth to `references/` subdirectory. |
| Frontmatter | `name` and `description` (pushy triggers that combat undertriggering) |
| On Activation | Read brand/ files with fallback behavior for missing files |
| Anti-Patterns | Include WHY each pattern is wrong, plus what to do instead |
| Progressive enhancement | Work at L0 (zero context), improve at L4 (full brand) |
| Voice file reference | Always `voice-profile.md`, never `voice.md` |
| Brand file paths | Always use `brand/` prefix in frontmatter reads |
| Frontmatter ↔ manifest | Frontmatter `reads:`/`writes:` use `brand/x.md`; `skills-manifest.json` uses bare `x.md`. Brand entries must match after prefix strip (non-brand outputs like `marketing/...` are ignored) — `bun scripts/check-brand-io.ts` prints drift, `tests/manifest-brand-io.test.ts` enforces it. |
### CORRECT vs WRONG
```yaml
# CORRECT frontmatter
---
name: seo-content
description: >-
Create SEO-optimized content. Use when writing blog posts, articles,
landing page copy, or any content targeting search rankings.
reads:
- brand/voice-profile.md
- brand/keyword-plan.md
---
```
```yaml
# WRONG: vague trigger, missing brand prefix, wrong voice file
---
name: seo-content
description: Helps with SEO content.
reads:
- voice.md
- keyword-plan.md
---
```
### Reference-Implementation Traceability Convention
Every pattern claim in a skill's `references/*.md` should cite an actual file path — a real repo, a real component, a real route — not a hypothetical. Pattern docs without traceability rot into vibes; agents stop trusting them after the third "see `src/components/Foo.tsx`" that doesn't exist.
| Rule | Detail |
|---|---|
| Cite live paths | When a pattern doc says "do it like this," it should follow with `Reference: <repo>/<path/to/file.ext>` or an inline link. Paths must exist at write time. |
| Worked examples beat abstract specs | Prefer "see how `skills/seo-machine/references/patterns/alternatives.md` references its template" over a generic explanation. |
| Update on rename | If the cited path moves, update the reference doc — same commit. A broken citation in references/ is documentation debt that compounds. |
| Multi-repo OK | Citations may point at sibling repos under `~/projects/mktgmono/` (e.g. `mktg-studio/`) or external open-source repos. Use repo-relative paths for internal, full GitHub URLs for external. |
This convention is borrowed from `skills/seo-machine/`'s `references/patterns/*.md`, which cites exact `marketing_controller.rb`, `AlternativeLayout.tsx`, and route paths in the reference implementation. Pattern docs that follow this discipline feel grounded; pattern docs that don't read like consultantware.
### Long-Arc Sprint Persistence Pattern
Some skills span weeks or months — they ship in N phases, each phase produces a deliverable, and the user resumes the work across sessions (e.g. `seo-machine` ships ~30 programmatic pages over 4-8 weeks). These skills follow the **sprint-persistence pattern**: a single canonical doc in the user's project that survives session interruption, context compaction, and worktree resets.
The canonical example is `docs/seo-machine.md` from the seo-machine skill. Any new long-arc skill should adopt this shape:
| Element | Detail |
|---|---|
| Canonical doc | One markdown file at `docs/<skill-name>.md` (or path the user picks during init). Single source of truth across sessions. |
| Phase Status Tracker | Table with `# | Phase | Pattern | Status | PR/Commit` columns. Status: `pending` → `in_progress` → `completed` (or `skipped` with reason). |
| Reference data block | Stable contracts: stack info, brand facts, paths to files the skill will edit. Marked "don't modify without explicit user instruction." |
| Resume protocol | Skill detects the doc on every invocation; if present, runs in Resume mode and picks the next pending phase. User does not need to remember where they left off. |
| Same-commit tracker update | When a phase ships, the tracker row update lands in the same commit as the phase work. Reviewers see both in one diff. |
| Config sidecar | Optional `.<skill-name>/config.json` for machine-readable state (chosen stack, project ID, etc.). Per-skill subdirectory under repo root. |
Skills that should use this pattern: any sprint, multi-page generator, or recurring audit loop that the user re-enters more than 3 times. Skills that should NOT use it: one-shot generators, single-artifact skills, anything that completes in one session.
## Drop-in Agent Contract
Add a new marketing agent without touching CLI code.
### Steps
1. Create the agent `.md` file in `agents/research/` or `agents/review/`
2. Add an entry to `agents-manifest.json` with: `category`, `file`, `reads`, `writes`, `skill`, `tier`
3. Run `mktg update` to install
### Agent Rules
| Rule | Detail |
|---|---|
| Never ask questions | Research, analyze, write. Autonomy is the default. |
| Never call other agents | `/cmo` orchestrates all agent coordination. |
| Write to `brand/` only | All output goes to the project's `brand/` directory. |
| Reference a skill | Each agent follows one skill's methodology. |
## Drop-in Catalog Contract
Add a new upstream catalog without touching CLI code. Catalogs are external OSS projects mktg builds on top of via REST API (not vendored source, not linked SDKs).
### Steps
1. Create an entry in `catalogs-manifest.json` under `catalogs.<name>` matching the `CatalogEntry` shape (see `src/types.ts`).
2. Implement whatever the `capabilities` advertise:
- `publish_adapters[]` → add the adapter to `ADAPTERS` and `ADAPTER_ENV_VARS` in `src/commands/publish.ts`.
- `scheduling_adapters[]` → reserved for cal.com-style catalogs; future.
- `email_adapters[]` → reserved for listmonk-style catalogs; future.
- `research_adapters[]` → research/data-plane catalogs (SEO metrics, SERP, backlinks). Wired via the companion skill + optional `mcp` block, not publish adapters.
- `skills[]` → create the corresponding `skills/<skill-name>/SKILL.md` and register in `skills-manifest.json`.
3. Run `mktg catalog list --json` to verify the entry loaded. `loadCatalogManifest` runs here and rejects adapter-name collisions (`CATALOG_COLLISION`), licenses outside the allowlist (`CATALOG_LICENSE_DENIED`), and malformed entries (`CATALOG_MANIFEST_INVALID`) as structured errors. (`mktg catalog sync` is the wrong command for this; it checks upstream version drift against GitHub releases, not manifest validity.)
4. (Optional) Run `mktg doctor --json` to verify env vars in `auth.credential_envs` are set for the new catalog.
### Catalog Entry Requirements
| Requirement | Detail |
|---|---|
| `name` | Lowercase, validated via `validateResourceId`: no `..`, no slashes, no spaces. |
| `license` | SPDX identifier. Matched against the allowlist in `src/core/catalogs.ts`. AGPL requires `transport: "http"`. |
| `version_pinned` | An upstream tag (not `main`). `mktg catalog sync` diffs this against latest. |
| `transport` | `"sdk"` ONLY if the upstream's SDK license permits linking. Otherwise `"http"` and `sdk_reference: null`. |
| `auth` | Fully declared: `style`, `base_env`, `credential_envs`, `header_format`. Skill and adapter code never hardcodes env names; it reads from the catalog entry. |
| `capabilities` | At least one non-empty capability array (`publish_adapters`, `scheduling_adapters`, `email_adapters`, `research_adapters`). A catalog with zero capabilities is rejected at load time. |
| `mcp` (optional) | `{ url_env, default_url }` for MCP-served catalogs (e.g. openseo). `default_url` must be https. `transport` stays `"http"` — MCP rides over HTTP; no new transport value. |
### Security Posture
> [!IMPORTANT]
> Catalogs are external services. All responses are untrusted input.
| Rule | Detail |
|---|---|
| License check | `loadCatalogManifest` rejects catalogs with licenses not on the allowlist, fails loudly. |
| Adapter collision | If two catalogs declare the same `capabilities.publish_adapters[i]`, load throws a named error, which prevents silent last-write-wins in `src/commands/publish.ts`. |
| Raw-fetch mandate | AGPL catalogs MUST use `transport: "http"`. A `package.json` test asserts banned SDK packages are absent. |
| Sandbox | State files for catalogs (sent-markers, caches) live under `.mktg/publish/` or `.mktg/cache/`, sandboxed via `sandboxPath()`. |
| Credential scope | Env vars only. Catalogs never persist credentials to `brand/` or repo-tracked files. |
| Frontmatter ↔ manifest drift | For catalog-contributed skills, `tests/integration/catalog-manifest.test.ts` asserts the skill's frontmatter `env_vars` equals `catalog.auth.base_env ∪ catalog.auth.credential_envs`. |
## Adding a CLI Command
Every new command in `src/commands/` must satisfy:
| Requirement | How |
|---|---|
| Typed return | `CommandResult<T>` with typed data |
| JSON output | `--json` flag, auto via `!isTTY()` |
| Dry run | `--dry-run` for mutating operations |
| Field filtering | `--fields` with dot-notation |
| Destructive guard | `--confirm` for destructive ops |
| Error guidance | Include `fix` field in error checks |
| Schema | Add entry in `schema.ts` with `responseSchema` |
| Streaming | `--ndjson` for list-like output |
After creating the handler, register it in `src/cli.ts`.
<!-- Track E (frostbyte) — added 2026-05-04 — Upstream-Mirrored Skill Contract -->
## Upstream-Mirrored Skill Contract
Some skills (e.g. `remotion-best-practices`) mirror an external upstream repository. Their bundled SKILL.md and `rules/` content are vendored from upstream and stay in sync via a provenance manifest plus a drift-checker script. **Distinct** from chained-in CLI skills (e.g. `firecrawl`) — those shell out to a separately-installed CLI binary and have no provenance manifest.
| Pattern | Provenance file | Drift checker | mktg integration | Example |
|---|---|---|---|---|
| Upstream-mirrored | `upstream.json` | `scripts/check-upstream.sh` | `mktg skill check-upstream`, `mktg skill upgrade`, `mktg doctor` | `remotion-best-practices` |
| Chained-in CLI | (none) | (n/a) | `mktg doctor` ecosystem table | `firecrawl` |
### upstream.json Schema
| Key | Type | Required | Purpose |
|---|---|---|---|
| `version` | number | yes | Schema version (currently `1`) |
| `fetched_at` | string (ISO 8601 UTC) | yes | When the snapshot was last refreshed. `mktg doctor` warns if > 30 days. |
| `tool` | string | yes | Tool that produced the snapshot (e.g. `"/mktg-steal"`) |
| `sources` | array | yes | One or more upstream sources mirrored into this skill |
| `sources[].name` | string | yes | Logical label, e.g. `"primary"` or `"secondary"` |
| `sources[].repo` | string | yes | GitHub `owner/repo` slug |
| `sources[].branch` | string | yes | Upstream branch tracked (typically `main`) |
| `sources[].snapshot_sha` | string | yes | Upstream commit SHA at last fetch |
| `sources[].snapshot_at` | string (ISO 8601) | optional | Per-source fetch timestamp (falls back to top-level `fetched_at`) |
| `sources[].upstream_root` | string | yes | Subtree path inside the upstream repo (e.g. `"skills/remotion"`) |
| `sources[].local_root` | string | yes | Path inside this repo where the mirror lives (e.g. `"skills/remotion-best-practices"`) |
| `sources[].files` | array | yes | Manifest of mirrored files (one entry per blob) |
| `sources[].files[].local` | string | yes | Local path relative to `local_root` |
| `sources[].files[].upstream` | string | yes | Upstream path including `upstream_root` prefix |
| `sources[].files[].sha` | string | yes | Upstream blob SHA recorded at snapshot time |
| `sources[].files[].note` | string | optional | Annotation; `"adapted-frontmatter"` flags files intentionally diverged from upstream — preserved across `mktg skill upgrade` |
The canonical reference is `skills/remotion-best-practices/upstream.json` — point at it when adding a new upstream-mirrored skill.
### Drift-Check Script Contract
Every upstream-mirrored skill ships an executable `scripts/check-upstream.sh`. Invoked from the skill directory (`cwd = skills/<name>/`), it **must**:
| Behavior | Detail |
|---|---|
| Read the manifest | `../upstream.json` is the source of truth; reject if missing or invalid JSON. |
| Fetch live tree | Use `gh api repos/<repo>/git/trees/<branch>:<upstream_root>?recursive=1` per source. |
| Compute drift | Compare each manifest entry's `sha` to the live blob SHA. Items with `note: "adapted-frontmatter"` surface in `drift.modified` but DO NOT flip `in_sync` to false. |
| Emit JSON to stdout | Shape: `{ ok, in_sync, checked_at, sources: [{ name, repo, snapshot_sha, current_sha, drift: { added, modified, removed } }] }` |
| Exit codes | `0` = no real drift; `1` = drift detected (added/removed/modified-without-note); `2` = environment error (missing `gh`/`jq`, manifest missing, network failure) |
### CLI Integration
| Command | Reads | Writes | Behavior |
|---|---|---|---|
| `mktg skill check-upstream [<name>] [--all]` | every `<skill>/upstream.json` + `scripts/check-upstream.sh` | nothing | Aggregates per-skill drift into a single envelope. Read-only. |
| `mktg skill upgrade <name> [--dry-run] [--confirm]` | `upstream.json`, gh API | local skill files; refreshes `upstream.json`; bumps `skills-manifest.json` version | Applies drift. Adapted-frontmatter files surface as `manual_merge_required` instead of auto-overwrite. Validates post-upgrade SKILL.md and rolls back on failure. Removals require `--confirm`. |
| `mktg doctor [--check-upstream]` | every `<skill>/upstream.json` | nothing | Default mode warns on `fetched_at > 30 days`. With `--check-upstream`, additionally invokes each skill's drift script for a live verdict. |
When porting a new upstream into mktg, the contract is: ship `upstream.json` + `scripts/check-upstream.sh`, and the three CLI commands above start working automatically. No CLI code changes — drop-in by design.
<!-- end Track E section -->
## 3-Way Sync (mktg-site mirror + Ai-Agent-Skills registry)
The canonical `skills-manifest.json` is the single source of truth. Two downstream surfaces stay in lockstep with it:
- `mktg-site/skills-manifest.json` — mirror with embedded `_changed_at` / `_first_seen` ISO timestamps. The website activity page reads these.
- `Ai-Agent-Skills/skills.json` — registry with curated metadata per skill (source, sourceUrl, tags, etc.).
- The sync command (`mktg-propagate`) lives in the **private** `mktg-private` repo, NOT in `marketing-cli` itself, because it's maintainer-only infra and would be useless to public npm users. See `MoizIbnYousaf/mktg-private` for the implementation. From this repo's perspective: just know that `/mktg-add` and `/mktg-steal` invoke it as their final step after committing to `marketing-cli`.
+ Downstream mirror publication is maintained outside the public product package. From this repo's
+ perspective, edit `skills-manifest.json` as the canonical source and treat downstream publication as
+ a separate maintainer action.
- ## Cursor Cloud specific instructions
+ ## Local and Amp Orb Development
### Runtime
- Package manager is **Bun** (`>=1.1.0`). Put `~/.bun/bin` and `~/.local/bin` on `PATH`.
- - Local CLI wrapper: `~/.local/bin/mktg` → `node /workspace/dist/cli.js` (fallback `bun src/cli.ts`). Rebuild after CLI changes: `bun run build`.
+ - Local CLI wrapper: `~/.local/bin/mktg` should resolve to this checkout's `dist/cli.js` (fallback `bun src/cli.ts`). Never hardcode `/workspace`; rebuild after CLI changes with `bun run build`.
- Install deps: `bun install` at repo root (hoisted linker via `bunfig.toml`). Skills/agents install via `mktg init` / `mktg update` into `~/.claude/skills` and `~/.claude/agents`.
### Services
| Surface | Command | Ports |
|---|---|---|
| CLI (dev) | `bun run dev -- <cmd> --json` or `mktg <cmd> --json` | n/a |
- | Studio API + Next | `mktg studio --no-open` (or `bun --cwd studio run start:studio`) | API `:3001`, dashboard `:3000` |
+ | Bundled Studio API + Next | `mktg studio --no-open` (or `bun run studio/bin/mktg-studio.ts --no-open`) | API `:3001`, dashboard `:3000` |
+ | Public website | Run `bun run dev` in `mktg-site/website` | Next dev port |
- Studio auth: bearer token at `~/.mktg/.runtime/studio-token`. Open `http://127.0.0.1:3000/dashboard?token=<token>` once so the dashboard stores it in `localStorage`. Public routes only: `/api/health`, `/api/schema`, `/api/help`.
+ Studio auth: bearer token at `~/.mktg/.runtime/studio-token`. A normal launcher supplies it to the dashboard on first navigation so the dashboard stores it in `localStorage`. Public API routes only: `/api/health`, `/api/schema`, `/api/help`. The orb-supervised service uses the server's explicit development-only auth escape hatch because the Amp portal is itself authenticated; do not use that setting for a network-exposed deployment.
### Gotchas discovered in cloud VMs
1. **Project root**: Studio must share `brand/` and `.mktg/native-publish/` with the CLI. The launcher now defaults `MKTG_PROJECT_ROOT` to the marketing-cli parent when `studio/` is a workspace member. Still override with `MKTG_PROJECT_ROOT=/path` when pointing at an external project.
- 2. **`mktg verify` / `mktg ship-check` suite reachability**: those commands resolve suites as `<mktgmonoRoot>/marketing-cli` and `.../mktg-studio`. In a bare `/workspace` checkout they mark suites unreachable. Symlink `sudo ln -sfn /workspace /marketing-cli` (or check out as `.../mktgmono/marketing-cli`) if you need those orchestration tests green.
+ 2. **`mktg verify` / `mktg ship-check` suite reachability**: those commands still resolve suites as `<mktgmonoRoot>/marketing-cli` and `.../mktg-studio`. In a standalone checkout they may mark the legacy Studio suite unreachable. Use the conventional sibling layout when running those cross-repo orchestration checks; `.agents/setup` creates the local `marketing-cli` workspace alias needed in orbs.
3. **Studio lint**: `bun --cwd studio run lint` expects an ESLint flat config (`eslint.config.*`) that is not present yet — typecheck via `bun --cwd studio run typecheck` instead.
4. **Optional integrations**: Postiz / Typefully / Resend / Firecrawl / Exa are BYO. Exa is first-class via skills `exa-search`, `exa-contents`, `company-research`, `lead-generation`, `build-with-exa` + root `.mcp.json`. Set `EXA_API_KEY`; `mktg doctor` surfaces `integration-EXA_API_KEY`.
5. **Tests**: root `bun test` (CLI); studio `bun run --cwd studio test` (or `bun run test` inside `studio/`). Do **not** run bare `bun test` in `studio/` — that also picks up `tests/e2e/perf` (Lighthouse fixtures) and fails without a prior `bun run tests/e2e/perf/run-lighthouse.ts`. No mocks — real temp-dir I/O.
- 6. **Studio UI vs API in cloud VMs**: Bun API on `:3001` is the source of truth. The Next dashboard on `:3000` rewrites `/api/*` → Bun; that proxy can hang (`socket hang up` / HTTP 000) while direct `:3001` curls stay healthy. If the UI stays on skeletons, verify with `curl -H "Authorization: Bearer $(cat ~/.mktg/.runtime/studio-token)" http://127.0.0.1:3001/api/skills` and restart via `mktg studio --no-open` with `MKTG_PROJECT_ROOT` set to the checkout root. Do not start `bun run server.ts` alone without that root — native publish / brand paths will miss `/workspace/.mktg`.
+ 6. **Studio UI vs API in cloud VMs**: Bun API on `:3001` is the source of truth. The Next dashboard on `:3000` rewrites `/api/*` → Bun; that proxy can hang (`socket hang up` / HTTP 000) while direct `:3001` curls stay healthy. If the UI stays on skeletons, verify with `curl -H "Authorization: Bearer $(cat ~/.mktg/.runtime/studio-token)" http://127.0.0.1:3001/api/skills` and restart via `mktg studio --no-open` with `MKTG_PROJECT_ROOT` set to the checkout root. Do not start `bun run server.ts` alone without that root — native publish and brand paths will point at the wrong `.mktg/` and `brand/` directories.
- 5. **`/axi`**: tool-interface orchestrator (parallel to `/cmo` for marketing). Prefer `npx -y gh-axi` / `npx -y chrome-devtools-axi` over eager MCP. See `skills/axi/SKILL.md`.
+ 7. **`/axi`**: tool-interface orchestrator (parallel to `/cmo` for marketing). Prefer `npx -y gh-axi` / `npx -y chrome-devtools-axi` over eager MCP. See `skills/axi/SKILL.md`.
### Standard commands
See `CONTRIBUTING.md` and `CONTEXT.md` for the full command surface. Quick loop:
```bash
bun install
bun run build
bun test
bun x tsc --noEmit
mktg init --json # skills + brand scaffold
mktg studio --no-open # dashboard + API
```