@mcptoolshop/brand 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 mcp-tool-shop
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.es.md ADDED
@@ -0,0 +1,69 @@
1
+ <p align="center">
2
+ <a href="README.ja.md">日本語</a> | <a href="README.zh.md">中文</a> | <a href="README.md">English</a> | <a href="README.fr.md">Français</a> | <a href="README.hi.md">हिन्दी</a> | <a href="README.it.md">Italiano</a> | <a href="README.pt-BR.md">Português (BR)</a>
3
+ </p>
4
+
5
+ <p align="center">
6
+ <img src="assets/logo.jpg" alt="Brand" width="400">
7
+ </p>
8
+
9
+ <p align="center">
10
+ <a href="https://github.com/mcp-tool-shop-org/brand/actions/workflows/ci.yml"><img src="https://github.com/mcp-tool-shop-org/brand/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
11
+ <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow" alt="License: MIT"></a>
12
+ <a href="https://mcp-tool-shop-org.github.io/brand/"><img src="https://img.shields.io/badge/Landing_Page-live-blue" alt="Landing Page"></a>
13
+ </p>
14
+
15
+ Registro centralizado de activos de marca para la organización GitHub [mcp-tool-shop-org](https://github.com/mcp-tool-shop-org). Un repositorio contiene todos los logotipos. Cada archivo README hace referencia a este repositorio. Actualiza una vez, y se actualiza en todas partes.
16
+
17
+ ## ¿Por qué?
18
+
19
+ Cuando cada repositorio tiene su propia copia del logotipo, se produce duplicación, desviación e inconsistencia. Una actualización de la marca implica buscar en más de 80 repositorios. Este repositorio soluciona ese problema: los logotipos se encuentran aquí, y los archivos README los referencian a través de URLs de `raw.githubusercontent.com`.
20
+
21
+ ## Estructura
22
+
23
+ ```
24
+ logos/
25
+ <slug>/
26
+ readme.png # or readme.jpg — format preserved as-is
27
+ manifest.json # SHA-256 integrity hashes for every asset
28
+ docs/
29
+ handbook.md # Lessons learned from migrating 80+ repos
30
+ ```
31
+
32
+ 81 logotipos distribuidos en 81 repositorios. Los archivos PNG permanecen como PNG y los archivos JPEG como JPEG. El formato es una decisión de la marca, no un objetivo de compilación.
33
+
34
+ ## Interfaz de línea de comandos (CLI)
35
+
36
+ ```bash
37
+ # Verify all logos match their manifest hashes
38
+ brand verify
39
+
40
+ # Regenerate manifest after adding/replacing a logo
41
+ brand manifest
42
+
43
+ # CI mode — fail if manifest is out of date
44
+ brand manifest --check
45
+
46
+ # Audit repos for broken refs, badge collisions, indentation traps
47
+ brand audit --repos /path/to/clones
48
+
49
+ # Migrate READMEs to point at brand repo (dry run first)
50
+ brand migrate --repos /path/to/clones --dry-run
51
+ brand migrate --repos /path/to/clones
52
+ ```
53
+
54
+ ## Añadir un nuevo logotipo
55
+
56
+ 1. Copia el archivo en `logos/<slug>/readme.png` (o `.jpg`).
57
+ 2. Ejecuta `brand manifest` para actualizar los hashes de integridad.
58
+ 3. Realiza un commit tanto del logotipo como de `manifest.json` juntos.
59
+ 4. El sistema de integración continua (CI) verifica el manifiesto al realizar un push.
60
+
61
+ ## Seguridad
62
+
63
+ Cada logotipo está rastreado mediante un hash SHA-256 en `manifest.json`. El sistema de integración continua (CI) ejecuta `brand manifest --check` en cada push que afecte a `logos/` o `manifest.json`. Cualquier discrepancia, ya sea una sobrescritura accidental, manipulación o desviación, provoca el fallo de la compilación.
64
+
65
+ Consulta [docs/handbook.md](docs/handbook.md) para obtener más información: por qué los enlaces simbólicos no funcionan, cómo los distintivos interfieren con la detección de logotipos, las trampas de renderizado de Markdown que rompen las etiquetas `<img>`, y el protocolo de seguridad para la migración.
66
+
67
+ ## Licencia
68
+
69
+ [MIT](LICENSE)
package/README.fr.md ADDED
@@ -0,0 +1,69 @@
1
+ <p align="center">
2
+ <a href="README.ja.md">日本語</a> | <a href="README.zh.md">中文</a> | <a href="README.es.md">Español</a> | <a href="README.md">English</a> | <a href="README.hi.md">हिन्दी</a> | <a href="README.it.md">Italiano</a> | <a href="README.pt-BR.md">Português (BR)</a>
3
+ </p>
4
+
5
+ <p align="center">
6
+ <img src="assets/logo.jpg" alt="Brand" width="400">
7
+ </p>
8
+
9
+ <p align="center">
10
+ <a href="https://github.com/mcp-tool-shop-org/brand/actions/workflows/ci.yml"><img src="https://github.com/mcp-tool-shop-org/brand/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
11
+ <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow" alt="License: MIT"></a>
12
+ <a href="https://mcp-tool-shop-org.github.io/brand/"><img src="https://img.shields.io/badge/Landing_Page-live-blue" alt="Landing Page"></a>
13
+ </p>
14
+
15
+ Registre centralisé des éléments de marque pour l'organisation GitHub [mcp-tool-shop-org](https://github.com/mcp-tool-shop-org). Un seul dépôt contient tous les logos. Chaque fichier README renvoie vers ce dépôt. Mettez à jour une seule fois, et la modification se propage partout.
16
+
17
+ ## Pourquoi ?
18
+
19
+ Lorsque chaque dépôt contient sa propre copie du logo, cela entraîne une duplication, des divergences et une incohérence. Une modification de la marque implique de parcourir plus de 80 dépôts. Ce dépôt résout ce problème : les logos sont stockés ici, et les fichiers README les référencent via les URL `raw.githubusercontent.com`.
20
+
21
+ ## Structure
22
+
23
+ ```
24
+ logos/
25
+ <slug>/
26
+ readme.png # or readme.jpg — format preserved as-is
27
+ manifest.json # SHA-256 integrity hashes for every asset
28
+ docs/
29
+ handbook.md # Lessons learned from migrating 80+ repos
30
+ ```
31
+
32
+ 81 logos répartis dans 81 dépôts. Les fichiers PNG restent des fichiers PNG. Les fichiers JPEG restent des fichiers JPEG. Le format est une décision de marque, et non une cible de construction.
33
+
34
+ ## Interface en ligne de commande (CLI)
35
+
36
+ ```bash
37
+ # Verify all logos match their manifest hashes
38
+ brand verify
39
+
40
+ # Regenerate manifest after adding/replacing a logo
41
+ brand manifest
42
+
43
+ # CI mode — fail if manifest is out of date
44
+ brand manifest --check
45
+
46
+ # Audit repos for broken refs, badge collisions, indentation traps
47
+ brand audit --repos /path/to/clones
48
+
49
+ # Migrate READMEs to point at brand repo (dry run first)
50
+ brand migrate --repos /path/to/clones --dry-run
51
+ brand migrate --repos /path/to/clones
52
+ ```
53
+
54
+ ## Ajouter un nouveau logo
55
+
56
+ 1. Déposez le fichier dans `logos/<slug>/readme.png` (ou `.jpg`)
57
+ 2. Exécutez `brand manifest` pour mettre à jour les hachages d'intégrité
58
+ 3. Validez à la fois le logo et `manifest.json` ensemble
59
+ 4. L'intégration continue (CI) vérifie le fichier manifest lors de chaque envoi
60
+
61
+ ## Sécurité
62
+
63
+ Chaque logo est suivi par un hachage SHA-256 dans `manifest.json`. L'intégration continue (CI) exécute `brand manifest --check` lors de chaque envoi qui modifie les fichiers `logos/` ou `manifest.json`. Toute incohérence – écrasement accidentel, manipulation, divergence – entraîne l'échec de la construction.
64
+
65
+ Consultez [docs/handbook.md](docs/handbook.md) pour plus de détails : pourquoi les liens symboliques ne fonctionnent pas, comment les badges interfèrent avec la détection des logos, les pièges de rendu Markdown qui corrompent les balises `<img>`, et le protocole de sécurité de migration.
66
+
67
+ ## Licence
68
+
69
+ [MIT](LICENSE)
package/README.hi.md ADDED
@@ -0,0 +1,69 @@
1
+ <p align="center">
2
+ <a href="README.ja.md">日本語</a> | <a href="README.zh.md">中文</a> | <a href="README.es.md">Español</a> | <a href="README.fr.md">Français</a> | <a href="README.md">English</a> | <a href="README.it.md">Italiano</a> | <a href="README.pt-BR.md">Português (BR)</a>
3
+ </p>
4
+
5
+ <p align="center">
6
+ <img src="assets/logo.jpg" alt="Brand" width="400">
7
+ </p>
8
+
9
+ <p align="center">
10
+ <a href="https://github.com/mcp-tool-shop-org/brand/actions/workflows/ci.yml"><img src="https://github.com/mcp-tool-shop-org/brand/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
11
+ <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow" alt="License: MIT"></a>
12
+ <a href="https://mcp-tool-shop-org.github.io/brand/"><img src="https://img.shields.io/badge/Landing_Page-live-blue" alt="Landing Page"></a>
13
+ </p>
14
+
15
+ [mcp-tool-shop-org](https://github.com/mcp-tool-shop-org) गिटहब संगठन के लिए केंद्रीकृत ब्रांड एसेट रजिस्ट्री। एक रिपॉजिटरी में सभी लोगो मौजूद हैं। प्रत्येक README फ़ाइल यहां लिंक करती है। एक बार अपडेट करें, और हर जगह अपडेट हो जाएगा।
16
+
17
+ ## क्यों?
18
+
19
+ जब प्रत्येक रिपॉजिटरी में लोगो की अपनी प्रति होती है, तो डुप्लीकेशन, विचलन और असंगति होती है। ब्रांडिंग में बदलाव करने का मतलब है 80 से अधिक रिपॉजिटरी में खोज करना। यह रिपॉजिटरी इस समस्या को हल करता है - लोगो यहां मौजूद हैं, और README फ़ाइलें `raw.githubusercontent.com` यूआरएल के माध्यम से उनका उल्लेख करती हैं।
20
+
21
+ ## संरचना
22
+
23
+ ```
24
+ logos/
25
+ <slug>/
26
+ readme.png # or readme.jpg — format preserved as-is
27
+ manifest.json # SHA-256 integrity hashes for every asset
28
+ docs/
29
+ handbook.md # Lessons learned from migrating 80+ repos
30
+ ```
31
+
32
+ 81 रिपॉजिटरी में 81 लोगो। PNG फ़ाइलें PNG ही रहेंगी, और JPEG फ़ाइलें JPEG ही रहेंगी। फ़ॉर्मेट एक ब्रांड संबंधी निर्णय है, न कि बिल्ड लक्ष्य।
33
+
34
+ ## कमांड-लाइन इंटरफेस (CLI)
35
+
36
+ ```bash
37
+ # Verify all logos match their manifest hashes
38
+ brand verify
39
+
40
+ # Regenerate manifest after adding/replacing a logo
41
+ brand manifest
42
+
43
+ # CI mode — fail if manifest is out of date
44
+ brand manifest --check
45
+
46
+ # Audit repos for broken refs, badge collisions, indentation traps
47
+ brand audit --repos /path/to/clones
48
+
49
+ # Migrate READMEs to point at brand repo (dry run first)
50
+ brand migrate --repos /path/to/clones --dry-run
51
+ brand migrate --repos /path/to/clones
52
+ ```
53
+
54
+ ## एक नया लोगो जोड़ना
55
+
56
+ 1. फ़ाइल को `logos/<slug>/readme.png` (या `.jpg`) में डालें।
57
+ 2. `brand manifest` कमांड चलाकर इंटीग्रिटी हैश अपडेट करें।
58
+ 3. लोगो और `manifest.json` दोनों को एक साथ कमिट करें।
59
+ 4. CI (निरंतर एकीकरण) सिस्टम, पुश करने पर मैनिफेस्ट को सत्यापित करता है।
60
+
61
+ ## सुरक्षा
62
+
63
+ प्रत्येक लोगो को `manifest.json` में SHA-256 हैश द्वारा ट्रैक किया जाता है। CI सिस्टम, `logos/` या `manifest.json` को प्रभावित करने वाले प्रत्येक पुश पर `brand manifest --check` कमांड चलाता है। किसी भी प्रकार की विसंगति - चाहे वह आकस्मिक ओवरराइट हो, छेड़छाड़ हो, या विचलन - बिल्ड को विफल कर देती है।
64
+
65
+ पूरे विवरण के लिए [docs/handbook.md](docs/handbook.md) देखें: इसमें बताया गया है कि सिंबोलिक लिंक क्यों काम नहीं करते हैं, बैज लोगो का पता लगाने के साथ कैसे टकराते हैं, और मार्कडाउन रेंडरिंग के कौन से जाल `<img>` टैग को तोड़ देते हैं, साथ ही माइग्रेशन सुरक्षा प्रोटोकॉल भी।
66
+
67
+ ## लाइसेंस
68
+
69
+ [MIT](LICENSE)
package/README.it.md ADDED
@@ -0,0 +1,69 @@
1
+ <p align="center">
2
+ <a href="README.ja.md">日本語</a> | <a href="README.zh.md">中文</a> | <a href="README.es.md">Español</a> | <a href="README.fr.md">Français</a> | <a href="README.hi.md">हिन्दी</a> | <a href="README.md">English</a> | <a href="README.pt-BR.md">Português (BR)</a>
3
+ </p>
4
+
5
+ <p align="center">
6
+ <img src="assets/logo.jpg" alt="Brand" width="400">
7
+ </p>
8
+
9
+ <p align="center">
10
+ <a href="https://github.com/mcp-tool-shop-org/brand/actions/workflows/ci.yml"><img src="https://github.com/mcp-tool-shop-org/brand/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
11
+ <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow" alt="License: MIT"></a>
12
+ <a href="https://mcp-tool-shop-org.github.io/brand/"><img src="https://img.shields.io/badge/Landing_Page-live-blue" alt="Landing Page"></a>
13
+ </p>
14
+
15
+ Registro centralizzato delle risorse del marchio per l'organizzazione GitHub [mcp-tool-shop-org](https://github.com/mcp-tool-shop-org). Un repository contiene tutti i loghi. Ogni file README fa riferimento a questo repository. Aggiornare una volta, aggiornare ovunque.
16
+
17
+ ## Perché
18
+
19
+ Quando ogni repository contiene una propria copia del logo, si verificano duplicazioni, discrepanze e incoerenze. Un cambiamento di immagine richiede di cercare in oltre 80 repository. Questo repository risolve questo problema: i loghi sono memorizzati qui, e i file README li richiamano tramite URL `raw.githubusercontent.com`.
20
+
21
+ ## Struttura
22
+
23
+ ```
24
+ logos/
25
+ <slug>/
26
+ readme.png # or readme.jpg — format preserved as-is
27
+ manifest.json # SHA-256 integrity hashes for every asset
28
+ docs/
29
+ handbook.md # Lessons learned from migrating 80+ repos
30
+ ```
31
+
32
+ 81 loghi distribuiti in 81 repository. I file PNG rimangono file PNG, i file JPEG rimangono file JPEG. Il formato è una decisione del marchio, non un obiettivo di compilazione.
33
+
34
+ ## Interfaccia a riga di comando (CLI)
35
+
36
+ ```bash
37
+ # Verify all logos match their manifest hashes
38
+ brand verify
39
+
40
+ # Regenerate manifest after adding/replacing a logo
41
+ brand manifest
42
+
43
+ # CI mode — fail if manifest is out of date
44
+ brand manifest --check
45
+
46
+ # Audit repos for broken refs, badge collisions, indentation traps
47
+ brand audit --repos /path/to/clones
48
+
49
+ # Migrate READMEs to point at brand repo (dry run first)
50
+ brand migrate --repos /path/to/clones --dry-run
51
+ brand migrate --repos /path/to/clones
52
+ ```
53
+
54
+ ## Aggiungere un nuovo logo
55
+
56
+ 1. Copiare il file in `logos/<slug>/readme.png` (o `.jpg`)
57
+ 2. Eseguire `brand manifest` per aggiornare gli hash di integrità
58
+ 3. Effettuare il commit sia del logo che di `manifest.json` insieme
59
+ 4. Il sistema di integrazione continua (CI) verifica il file manifest ad ogni commit.
60
+
61
+ ## Sicurezza
62
+
63
+ Ogni logo è tracciato tramite l'hash SHA-256 nel file `manifest.json`. Il sistema CI esegue `brand manifest --check` ad ogni commit che modifica i file in `logos/` o `manifest.json`. Qualsiasi discrepanza (sovrascrittura accidentale, manomissione, discrepanza) causa il fallimento della compilazione.
64
+
65
+ Consultare [docs/handbook.md](docs/handbook.md) per maggiori dettagli: perché i collegamenti simbolici non funzionano, come i badge interferiscono con il rilevamento dei loghi, le trappole di rendering Markdown che compromettono i tag `<img>`, e il protocollo di sicurezza per la migrazione.
66
+
67
+ ## Licenza
68
+
69
+ [MIT](LICENSE)
package/README.ja.md ADDED
@@ -0,0 +1,69 @@
1
+ <p align="center">
2
+ <a href="README.md">English</a> | <a href="README.zh.md">中文</a> | <a href="README.es.md">Español</a> | <a href="README.fr.md">Français</a> | <a href="README.hi.md">हिन्दी</a> | <a href="README.it.md">Italiano</a> | <a href="README.pt-BR.md">Português (BR)</a>
3
+ </p>
4
+
5
+ <p align="center">
6
+ <img src="assets/logo.jpg" alt="Brand" width="400">
7
+ </p>
8
+
9
+ <p align="center">
10
+ <a href="https://github.com/mcp-tool-shop-org/brand/actions/workflows/ci.yml"><img src="https://github.com/mcp-tool-shop-org/brand/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
11
+ <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow" alt="License: MIT"></a>
12
+ <a href="https://mcp-tool-shop-org.github.io/brand/"><img src="https://img.shields.io/badge/Landing_Page-live-blue" alt="Landing Page"></a>
13
+ </p>
14
+
15
+ [mcp-tool-shop-org] GitHub組織向けの、ブランドアセットの一元管理レジストリです。このリポジトリには、すべてのロゴが格納されています。各READMEファイルは、このリポジトリを指しています。一度更新すれば、すべての場所で更新されます。
16
+
17
+ ## なぜか
18
+
19
+ 各リポジトリが独自のロゴコピーを持っていると、重複、乖離、および不整合が発生します。ブランド変更を行うには、80以上のリポジトリを個別に確認する必要があります。このリポジトリはその問題を解決します。ロゴはここに保存され、READMEファイルは`raw.githubusercontent.com`のURLを通じてそれらを参照します。
20
+
21
+ ## 構造
22
+
23
+ ```
24
+ logos/
25
+ <slug>/
26
+ readme.png # or readme.jpg — format preserved as-is
27
+ manifest.json # SHA-256 integrity hashes for every asset
28
+ docs/
29
+ handbook.md # Lessons learned from migrating 80+ repos
30
+ ```
31
+
32
+ 81個のロゴが、81個のリポジトリに分散して保存されています。PNGファイルはPNGのまま、JPEGファイルはJPEGのままです。ファイル形式は、ビルドの対象ではなく、ブランド側の決定です。
33
+
34
+ ## CLI (コマンドラインインターフェース)
35
+
36
+ ```bash
37
+ # Verify all logos match their manifest hashes
38
+ brand verify
39
+
40
+ # Regenerate manifest after adding/replacing a logo
41
+ brand manifest
42
+
43
+ # CI mode — fail if manifest is out of date
44
+ brand manifest --check
45
+
46
+ # Audit repos for broken refs, badge collisions, indentation traps
47
+ brand audit --repos /path/to/clones
48
+
49
+ # Migrate READMEs to point at brand repo (dry run first)
50
+ brand migrate --repos /path/to/clones --dry-run
51
+ brand migrate --repos /path/to/clones
52
+ ```
53
+
54
+ ## 新しいロゴの追加
55
+
56
+ 1. ファイルを`logos/<slug>/readme.png`(または`.jpg`)に配置します。
57
+ 2. `brand manifest`コマンドを実行して、整合性ハッシュを更新します。
58
+ 3. ロゴと`manifest.json`ファイルを一緒にコミットします。
59
+ 4. CI(継続的インテグレーション)は、プッシュ時に`manifest.json`の内容を検証します。
60
+
61
+ ## セキュリティ
62
+
63
+ すべてのロゴは、`manifest.json`ファイル内のSHA-256ハッシュによって追跡されます。CIは、`logos/`または`manifest.json`に影響を与えるすべてのプッシュに対して、`brand manifest --check`コマンドを実行します。不整合(意図しない上書き、改ざん、乖離など)が発生した場合、ビルドは失敗します。
64
+
65
+ 詳細については、[docs/handbook.md](docs/handbook.md)を参照してください。シンボリックリンクが機能しない理由、バッジがロゴ検出と競合する理由、`<img>`タグを壊すMarkdownレンダリングの問題、および移行の安全プロトコルについて説明されています。
66
+
67
+ ## ライセンス
68
+
69
+ [MIT](LICENSE)
package/README.md ADDED
@@ -0,0 +1,69 @@
1
+ <p align="center">
2
+ <a href="README.ja.md">日本語</a> | <a href="README.zh.md">中文</a> | <a href="README.es.md">Español</a> | <a href="README.fr.md">Français</a> | <a href="README.hi.md">हिन्दी</a> | <a href="README.it.md">Italiano</a> | <a href="README.pt-BR.md">Português (BR)</a>
3
+ </p>
4
+
5
+ <p align="center">
6
+ <img src="assets/logo.jpg" alt="Brand" width="400">
7
+ </p>
8
+
9
+ <p align="center">
10
+ <a href="https://github.com/mcp-tool-shop-org/brand/actions/workflows/ci.yml"><img src="https://github.com/mcp-tool-shop-org/brand/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
11
+ <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow" alt="License: MIT"></a>
12
+ <a href="https://mcp-tool-shop-org.github.io/brand/"><img src="https://img.shields.io/badge/Landing_Page-live-blue" alt="Landing Page"></a>
13
+ </p>
14
+
15
+ Centralized brand asset registry for the [mcp-tool-shop-org](https://github.com/mcp-tool-shop-org) GitHub org. One repo holds every logo. Every README points here. Update once, update everywhere.
16
+
17
+ ## Why
18
+
19
+ When every repo carries its own copy of the logo, you get duplication, drift, and inconsistency. A rebrand means hunting through 80+ repos. This repo fixes that — logos live here, READMEs reference them via `raw.githubusercontent.com` URLs.
20
+
21
+ ## Structure
22
+
23
+ ```
24
+ logos/
25
+ <slug>/
26
+ readme.png # or readme.jpg — format preserved as-is
27
+ manifest.json # SHA-256 integrity hashes for every asset
28
+ docs/
29
+ handbook.md # Lessons learned from migrating 80+ repos
30
+ ```
31
+
32
+ 81 logos across 81 repos. PNGs stay PNGs. JPEGs stay JPEGs. Format is a brand decision, not a build target.
33
+
34
+ ## CLI
35
+
36
+ ```bash
37
+ # Verify all logos match their manifest hashes
38
+ brand verify
39
+
40
+ # Regenerate manifest after adding/replacing a logo
41
+ brand manifest
42
+
43
+ # CI mode — fail if manifest is out of date
44
+ brand manifest --check
45
+
46
+ # Audit repos for broken refs, badge collisions, indentation traps
47
+ brand audit --repos /path/to/clones
48
+
49
+ # Migrate READMEs to point at brand repo (dry run first)
50
+ brand migrate --repos /path/to/clones --dry-run
51
+ brand migrate --repos /path/to/clones
52
+ ```
53
+
54
+ ## Adding a New Logo
55
+
56
+ 1. Drop the file into `logos/<slug>/readme.png` (or `.jpg`)
57
+ 2. Run `brand manifest` to update integrity hashes
58
+ 3. Commit both the logo and `manifest.json` together
59
+ 4. CI verifies the manifest on push
60
+
61
+ ## Security
62
+
63
+ Every logo is tracked by SHA-256 hash in `manifest.json`. CI runs `brand manifest --check` on every push that touches `logos/` or `manifest.json`. Any mismatch — accidental overwrite, tampering, drift — fails the build.
64
+
65
+ See [docs/handbook.md](docs/handbook.md) for the full story: why symlinks don't work, how badges collide with logo detection, the markdown rendering traps that break `<img>` tags, and the migration safety protocol.
66
+
67
+ ## License
68
+
69
+ [MIT](LICENSE)
@@ -0,0 +1,69 @@
1
+ <p align="center">
2
+ <a href="README.ja.md">日本語</a> | <a href="README.zh.md">中文</a> | <a href="README.es.md">Español</a> | <a href="README.fr.md">Français</a> | <a href="README.hi.md">हिन्दी</a> | <a href="README.it.md">Italiano</a> | <a href="README.md">English</a>
3
+ </p>
4
+
5
+ <p align="center">
6
+ <img src="assets/logo.jpg" alt="Brand" width="400">
7
+ </p>
8
+
9
+ <p align="center">
10
+ <a href="https://github.com/mcp-tool-shop-org/brand/actions/workflows/ci.yml"><img src="https://github.com/mcp-tool-shop-org/brand/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
11
+ <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow" alt="License: MIT"></a>
12
+ <a href="https://mcp-tool-shop-org.github.io/brand/"><img src="https://img.shields.io/badge/Landing_Page-live-blue" alt="Landing Page"></a>
13
+ </p>
14
+
15
+ Registro centralizado de ativos da marca para a organização GitHub [mcp-tool-shop-org](https://github.com/mcp-tool-shop-org). Um repositório contém todos os logotipos. Cada arquivo README aponta para este repositório. Atualize uma vez e atualize em todos os lugares.
16
+
17
+ ## Por que?
18
+
19
+ Quando cada repositório possui sua própria cópia do logotipo, você obtém duplicação, desvio e inconsistência. Uma mudança de marca significa procurar em mais de 80 repositórios. Este repositório resolve isso: os logotipos ficam aqui, e os arquivos README fazem referência a eles por meio de URLs `raw.githubusercontent.com`.
20
+
21
+ ## Estrutura
22
+
23
+ ```
24
+ logos/
25
+ <slug>/
26
+ readme.png # or readme.jpg — format preserved as-is
27
+ manifest.json # SHA-256 integrity hashes for every asset
28
+ docs/
29
+ handbook.md # Lessons learned from migrating 80+ repos
30
+ ```
31
+
32
+ 81 logotipos em 81 repositórios. Os arquivos PNG permanecem como PNGs. Os arquivos JPEG permanecem como JPEGs. O formato é uma decisão da marca, não um alvo de compilação.
33
+
34
+ ## Interface de Linha de Comando (CLI)
35
+
36
+ ```bash
37
+ # Verify all logos match their manifest hashes
38
+ brand verify
39
+
40
+ # Regenerate manifest after adding/replacing a logo
41
+ brand manifest
42
+
43
+ # CI mode — fail if manifest is out of date
44
+ brand manifest --check
45
+
46
+ # Audit repos for broken refs, badge collisions, indentation traps
47
+ brand audit --repos /path/to/clones
48
+
49
+ # Migrate READMEs to point at brand repo (dry run first)
50
+ brand migrate --repos /path/to/clones --dry-run
51
+ brand migrate --repos /path/to/clones
52
+ ```
53
+
54
+ ## Adicionando um Novo Logotipo
55
+
56
+ 1. Arraste o arquivo para `logos/<slug>/readme.png` (ou `.jpg`)
57
+ 2. Execute `brand manifest` para atualizar os hashes de integridade
58
+ 3. Faça o commit tanto do logotipo quanto do `manifest.json` juntos
59
+ 4. O sistema de integração contínua (CI) verifica o manifesto a cada envio.
60
+
61
+ ## Segurança
62
+
63
+ Cada logotipo é rastreado por um hash SHA-256 no `manifest.json`. O CI executa `brand manifest --check` em cada envio que afeta os arquivos `logos/` ou `manifest.json`. Qualquer incompatibilidade – sobrescrita acidental, adulteração, desvio – causa a falha da compilação.
64
+
65
+ Consulte [docs/handbook.md](docs/handbook.md) para obter mais detalhes: por que os links simbólicos não funcionam, como os selos interferem na detecção de logotipos, as armadilhas de renderização de Markdown que quebram as tags `<img>`, e o protocolo de segurança para migração.
66
+
67
+ ## Licença
68
+
69
+ [MIT](LICENSE)
package/README.zh.md ADDED
@@ -0,0 +1,69 @@
1
+ <p align="center">
2
+ <a href="README.ja.md">日本語</a> | <a href="README.md">English</a> | <a href="README.es.md">Español</a> | <a href="README.fr.md">Français</a> | <a href="README.hi.md">हिन्दी</a> | <a href="README.it.md">Italiano</a> | <a href="README.pt-BR.md">Português (BR)</a>
3
+ </p>
4
+
5
+ <p align="center">
6
+ <img src="assets/logo.jpg" alt="Brand" width="400">
7
+ </p>
8
+
9
+ <p align="center">
10
+ <a href="https://github.com/mcp-tool-shop-org/brand/actions/workflows/ci.yml"><img src="https://github.com/mcp-tool-shop-org/brand/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
11
+ <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow" alt="License: MIT"></a>
12
+ <a href="https://mcp-tool-shop-org.github.io/brand/"><img src="https://img.shields.io/badge/Landing_Page-live-blue" alt="Landing Page"></a>
13
+ </p>
14
+
15
+ 为 [mcp-tool-shop-org](https://github.com/mcp-tool-shop-org) GitHub 组织提供的集中式品牌资产注册库。一个仓库包含所有 logo。每个 README 文件都指向这里。只需更新一次,即可在所有地方同步更新。
16
+
17
+ ## 原因
18
+
19
+ 当每个仓库都包含自己的 logo 副本时,就会出现重复、偏差和不一致的情况。品牌重塑意味着需要在 80 多个仓库中进行搜索。这个仓库解决了这个问题——所有 logo 都存储在这里,README 文件通过 `raw.githubusercontent.com` URL 链接到它们。
20
+
21
+ ## 结构
22
+
23
+ ```
24
+ logos/
25
+ <slug>/
26
+ readme.png # or readme.jpg — format preserved as-is
27
+ manifest.json # SHA-256 integrity hashes for every asset
28
+ docs/
29
+ handbook.md # Lessons learned from migrating 80+ repos
30
+ ```
31
+
32
+ 81 个仓库,每个仓库包含一个 logo。PNG 格式的 logo 保持 PNG 格式,JPEG 格式的 logo 保持 JPEG 格式。文件格式是品牌决策,而不是构建目标。
33
+
34
+ ## 命令行工具 (CLI)
35
+
36
+ ```bash
37
+ # Verify all logos match their manifest hashes
38
+ brand verify
39
+
40
+ # Regenerate manifest after adding/replacing a logo
41
+ brand manifest
42
+
43
+ # CI mode — fail if manifest is out of date
44
+ brand manifest --check
45
+
46
+ # Audit repos for broken refs, badge collisions, indentation traps
47
+ brand audit --repos /path/to/clones
48
+
49
+ # Migrate READMEs to point at brand repo (dry run first)
50
+ brand migrate --repos /path/to/clones --dry-run
51
+ brand migrate --repos /path/to/clones
52
+ ```
53
+
54
+ ## 添加新的 logo
55
+
56
+ 1. 将文件放入 `logos/<slug>/readme.png` (或 `.jpg`) 目录中。
57
+ 2. 运行 `brand manifest` 命令以更新完整性哈希值。
58
+ 3. 同时提交 logo 文件和 `manifest.json` 文件。
59
+ 4. CI 系统在每次提交时验证 `manifest.json` 文件的内容。
60
+
61
+ ## 安全性
62
+
63
+ 每个 logo 都通过 SHA-256 哈希值在 `manifest.json` 文件中进行跟踪。CI 系统在每次涉及 `logos/` 目录或 `manifest.json` 文件的提交时,都会运行 `brand manifest --check` 命令。任何不匹配的情况——意外覆盖、篡改或偏差——都会导致构建失败。
64
+
65
+ 请参阅 [docs/handbook.md](docs/handbook.md) 获取完整信息:为什么符号链接不可用,徽章如何与 logo 检测冲突,以及 markdown 渲染中可能导致 `<img>` 标签失效的陷阱,以及迁移的安全协议。
66
+
67
+ ## 许可证
68
+
69
+ [MIT](LICENSE)
package/dist/cli.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * brand CLI — Centralized brand asset management.
4
+ *
5
+ * Commands:
6
+ * collect Scan org repos, collect authoritative logos
7
+ * migrate Rewrite README logo references to brand repo
8
+ * audit Scan for broken refs, badge collisions, indentation traps
9
+ * verify Verify logo integrity against manifest
10
+ * manifest Regenerate manifest.json
11
+ * push Commit + push README changes across repos
12
+ * revert Revert a migration by commit message
13
+ */
14
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * brand CLI — Centralized brand asset management.
4
+ *
5
+ * Commands:
6
+ * collect Scan org repos, collect authoritative logos
7
+ * migrate Rewrite README logo references to brand repo
8
+ * audit Scan for broken refs, badge collisions, indentation traps
9
+ * verify Verify logo integrity against manifest
10
+ * manifest Regenerate manifest.json
11
+ * push Commit + push README changes across repos
12
+ * revert Revert a migration by commit message
13
+ */
14
+ import { Command } from 'commander';
15
+ import { readFileSync } from 'node:fs';
16
+ import { join, dirname } from 'node:path';
17
+ import { fileURLToPath } from 'node:url';
18
+ const __dirname = dirname(fileURLToPath(import.meta.url));
19
+ let version = '0.1.0';
20
+ try {
21
+ const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
22
+ version = pkg.version;
23
+ }
24
+ catch {
25
+ // use default
26
+ }
27
+ const program = new Command();
28
+ program
29
+ .name('brand')
30
+ .description('Centralized brand asset management — migration, audit, and integrity verification')
31
+ .version(version);
32
+ program
33
+ .command('verify')
34
+ .description('Verify logo integrity against manifest')
35
+ .option('--manifest <path>', 'Path to manifest.json', 'manifest.json')
36
+ .option('--logos <path>', 'Path to logos directory', 'logos')
37
+ .action(async (opts) => {
38
+ const { runVerify } = await import('./commands/verify.js');
39
+ await runVerify(opts);
40
+ });
41
+ program
42
+ .command('manifest')
43
+ .description('Regenerate manifest.json from current logos')
44
+ .option('--logos <path>', 'Path to logos directory', 'logos')
45
+ .option('--output <path>', 'Output path for manifest', 'manifest.json')
46
+ .option('--check', 'Check mode — fail if manifest would change (for CI)')
47
+ .action(async (opts) => {
48
+ const { runManifest } = await import('./commands/manifest-cmd.js');
49
+ await runManifest(opts);
50
+ });
51
+ program
52
+ .command('audit')
53
+ .description('Scan org repos for broken logo refs, badge collisions, indentation traps')
54
+ .option('--repos <path>', 'Parent directory containing repo clones', '.')
55
+ .option('--brand-base <url>', 'Base URL for brand assets', 'https://raw.githubusercontent.com/mcp-tool-shop-org/brand/main')
56
+ .action(async (opts) => {
57
+ const { runAudit } = await import('./commands/audit.js');
58
+ await runAudit(opts);
59
+ });
60
+ program
61
+ .command('migrate')
62
+ .description('Rewrite README logo references to point at brand repo')
63
+ .option('--repos <path>', 'Parent directory containing repo clones', '.')
64
+ .option('--logos <path>', 'Path to logos directory', 'logos')
65
+ .option('--brand-base <url>', 'Base URL for brand logos', 'https://raw.githubusercontent.com/mcp-tool-shop-org/brand/main/logos')
66
+ .option('--dry-run', 'Preview changes without modifying files', false)
67
+ .action(async (opts) => {
68
+ const { runMigrate } = await import('./commands/migrate.js');
69
+ await runMigrate(opts);
70
+ });
71
+ program.parse();
@@ -0,0 +1,6 @@
1
+ interface AuditOptions {
2
+ repos: string;
3
+ brandBase: string;
4
+ }
5
+ export declare function runAudit(opts: AuditOptions): Promise<void>;
6
+ export {};
@@ -0,0 +1,104 @@
1
+ import chalk from 'chalk';
2
+ import { readFileSync, existsSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { globSync } from 'glob';
5
+ import { findLogoImgTags } from '../utils/readme-parser.js';
6
+ export async function runAudit(opts) {
7
+ const logosDir = join(opts.repos, 'logos');
8
+ const issues = [];
9
+ // Get all logo slugs
10
+ const slugDirs = globSync('*/', { cwd: logosDir }).map(d => d.replace(/\/$/, ''));
11
+ for (const slug of slugDirs) {
12
+ const repoDir = join(opts.repos, '..', slug);
13
+ if (!existsSync(repoDir))
14
+ continue;
15
+ // Find README files
16
+ const readmes = globSync('README*.md', { cwd: repoDir });
17
+ if (readmes.length === 0) {
18
+ issues.push({ repo: slug, file: '-', issue: 'no-readme', detail: 'No README.md found' });
19
+ continue;
20
+ }
21
+ for (const readmeFile of readmes) {
22
+ const readmePath = join(repoDir, readmeFile);
23
+ const content = readFileSync(readmePath, 'utf-8');
24
+ const lines = content.split('\n');
25
+ // Check: does the README reference the brand repo?
26
+ const logoMatches = findLogoImgTags(content);
27
+ if (logoMatches.length === 0 && readmeFile === 'README.md') {
28
+ issues.push({ repo: slug, file: readmeFile, issue: 'no-logo-ref', detail: 'No logo <img> tag found' });
29
+ continue;
30
+ }
31
+ for (const match of logoMatches) {
32
+ // Check: is the src pointing at the brand repo?
33
+ if (!match.src.includes('brand/main/logos')) {
34
+ issues.push({
35
+ repo: slug,
36
+ file: readmeFile,
37
+ issue: 'local-logo-ref',
38
+ line: match.line,
39
+ detail: `Still points to: ${match.src}`,
40
+ });
41
+ }
42
+ // Check: indentation trap (4+ spaces before <img)
43
+ const lineContent = match.content;
44
+ const leadingSpaces = lineContent.match(/^(\s*)/)?.[1]?.length ?? 0;
45
+ if (leadingSpaces >= 4 && !lineContent.trimStart().startsWith('<p')) {
46
+ issues.push({
47
+ repo: slug,
48
+ file: readmeFile,
49
+ issue: 'indentation-trap',
50
+ line: match.line,
51
+ detail: `${leadingSpaces} spaces of indentation — will render as code block`,
52
+ });
53
+ }
54
+ }
55
+ // Check: multiple logo matches (possible badge collision)
56
+ if (logoMatches.length > 1) {
57
+ issues.push({
58
+ repo: slug,
59
+ file: readmeFile,
60
+ issue: 'multiple-logo-matches',
61
+ detail: `${logoMatches.length} logo <img> tags found — possible badge collision`,
62
+ });
63
+ }
64
+ // Check: brand URL that 404s (format mismatch)
65
+ for (const match of logoMatches) {
66
+ if (match.src.includes('brand/main/logos')) {
67
+ const expectedPng = join(logosDir, slug, 'readme.png');
68
+ const expectedJpg = join(logosDir, slug, 'readme.jpg');
69
+ if (!existsSync(expectedPng) && !existsSync(expectedJpg)) {
70
+ issues.push({
71
+ repo: slug,
72
+ file: readmeFile,
73
+ issue: 'missing-brand-asset',
74
+ line: match.line,
75
+ detail: `Brand asset not found for slug: ${slug}`,
76
+ });
77
+ }
78
+ }
79
+ }
80
+ }
81
+ }
82
+ // Report
83
+ if (issues.length === 0) {
84
+ console.log(chalk.green(`\n ✓ Audit clean — ${slugDirs.length} repos checked, no issues.\n`));
85
+ return;
86
+ }
87
+ console.log(chalk.yellow(`\n Found ${issues.length} issue(s) across ${new Set(issues.map(i => i.repo)).size} repos:\n`));
88
+ const grouped = new Map();
89
+ for (const issue of issues) {
90
+ const key = issue.repo;
91
+ if (!grouped.has(key))
92
+ grouped.set(key, []);
93
+ grouped.get(key).push(issue);
94
+ }
95
+ for (const [repo, repoIssues] of grouped) {
96
+ console.log(chalk.white(` ${repo}`));
97
+ for (const issue of repoIssues) {
98
+ const lineRef = issue.line ? `:${issue.line}` : '';
99
+ const color = issue.issue.includes('trap') || issue.issue.includes('collision') ? chalk.red : chalk.yellow;
100
+ console.log(color(` [${issue.issue}] ${issue.file}${lineRef} — ${issue.detail}`));
101
+ }
102
+ }
103
+ console.log('');
104
+ }
@@ -0,0 +1,7 @@
1
+ interface ManifestOptions {
2
+ logos: string;
3
+ output: string;
4
+ check?: boolean;
5
+ }
6
+ export declare function runManifest(opts: ManifestOptions): Promise<void>;
7
+ export {};
@@ -0,0 +1,49 @@
1
+ import chalk from 'chalk';
2
+ import { existsSync } from 'node:fs';
3
+ import { generateManifest, writeManifest, readManifest } from '../manifest.js';
4
+ export async function runManifest(opts) {
5
+ const current = generateManifest(opts.logos);
6
+ if (opts.check) {
7
+ // CI mode: fail if manifest would change
8
+ if (!existsSync(opts.output)) {
9
+ console.error(chalk.red(' ✗ No manifest found. Run `brand manifest` to generate one.'));
10
+ process.exit(1);
11
+ }
12
+ const stored = readManifest(opts.output);
13
+ const storedKeys = Object.keys(stored.assets).sort();
14
+ const currentKeys = Object.keys(current.assets).sort();
15
+ let drift = false;
16
+ // Check for key differences
17
+ const storedSet = new Set(storedKeys);
18
+ const currentSet = new Set(currentKeys);
19
+ for (const key of currentSet) {
20
+ if (!storedSet.has(key)) {
21
+ console.error(chalk.yellow(` + ${key} (new, not in manifest)`));
22
+ drift = true;
23
+ }
24
+ }
25
+ for (const key of storedSet) {
26
+ if (!currentSet.has(key)) {
27
+ console.error(chalk.red(` × ${key} (removed, still in manifest)`));
28
+ drift = true;
29
+ }
30
+ }
31
+ // Check hash differences
32
+ for (const key of currentKeys) {
33
+ if (storedSet.has(key) && stored.assets[key].hash !== current.assets[key].hash) {
34
+ console.error(chalk.red(` ~ ${key} (hash changed)`));
35
+ drift = true;
36
+ }
37
+ }
38
+ if (drift) {
39
+ console.error(chalk.red('\n ✗ Manifest is out of date. Run `brand manifest` to update.\n'));
40
+ process.exit(1);
41
+ }
42
+ console.log(chalk.green(` ✓ Manifest is up to date (${currentKeys.length} assets).\n`));
43
+ return;
44
+ }
45
+ // Generate mode: write manifest
46
+ writeManifest(current, opts.output);
47
+ const count = Object.keys(current.assets).length;
48
+ console.log(chalk.green(` ✓ Manifest written: ${opts.output} (${count} assets)\n`));
49
+ }
@@ -0,0 +1,8 @@
1
+ interface MigrateOptions {
2
+ repos: string;
3
+ logos: string;
4
+ brandBase: string;
5
+ dryRun: boolean;
6
+ }
7
+ export declare function runMigrate(opts: MigrateOptions): Promise<void>;
8
+ export {};
@@ -0,0 +1,61 @@
1
+ import chalk from 'chalk';
2
+ import { readFileSync, writeFileSync, existsSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { globSync } from 'glob';
5
+ import { findLogoImgTags, rewriteLogoSrc } from '../utils/readme-parser.js';
6
+ export async function runMigrate(opts) {
7
+ const logosDir = opts.logos;
8
+ const slugDirs = globSync('*/', { cwd: logosDir }).map(d => d.replace(/\/$/, ''));
9
+ let total = 0;
10
+ let updated = 0;
11
+ let skipped = 0;
12
+ if (opts.dryRun) {
13
+ console.log(chalk.cyan('\n DRY RUN — no files will be modified.\n'));
14
+ }
15
+ for (const slug of slugDirs) {
16
+ const repoDir = join(opts.repos, '..', slug);
17
+ if (!existsSync(repoDir)) {
18
+ skipped++;
19
+ continue;
20
+ }
21
+ total++;
22
+ // Determine the correct file extension for this logo
23
+ let ext = 'png';
24
+ if (existsSync(join(logosDir, slug, 'readme.jpg')))
25
+ ext = 'jpg';
26
+ const newSrc = `${opts.brandBase}/${slug}/readme.${ext}`;
27
+ const readmes = globSync('README*.md', { cwd: repoDir });
28
+ let repoChanged = false;
29
+ for (const readmeFile of readmes) {
30
+ const readmePath = join(repoDir, readmeFile);
31
+ const content = readFileSync(readmePath, 'utf-8');
32
+ const matches = findLogoImgTags(content);
33
+ if (matches.length === 0)
34
+ continue;
35
+ // Check if already pointing at brand repo
36
+ const needsUpdate = matches.some(m => !m.src.includes('brand/main/logos'));
37
+ if (!needsUpdate)
38
+ continue;
39
+ if (opts.dryRun) {
40
+ for (const match of matches) {
41
+ if (!match.src.includes('brand/main/logos')) {
42
+ console.log(` ~ ${slug}/${readmeFile}`);
43
+ console.log(chalk.red(` old: ${match.src}`));
44
+ console.log(chalk.green(` new: ${newSrc}`));
45
+ }
46
+ }
47
+ }
48
+ else {
49
+ const rewritten = rewriteLogoSrc(content, newSrc);
50
+ writeFileSync(readmePath, rewritten, 'utf-8');
51
+ console.log(chalk.green(` ✓ ${slug}/${readmeFile}`));
52
+ }
53
+ repoChanged = true;
54
+ }
55
+ if (repoChanged)
56
+ updated++;
57
+ }
58
+ console.log(`\n Repos scanned: ${total}`);
59
+ console.log(` Repos updated: ${updated}`);
60
+ console.log(` Repos skipped: ${skipped} (no local clone)\n`);
61
+ }
@@ -0,0 +1,6 @@
1
+ interface VerifyOptions {
2
+ manifest: string;
3
+ logos: string;
4
+ }
5
+ export declare function runVerify(opts: VerifyOptions): Promise<void>;
6
+ export {};
@@ -0,0 +1,39 @@
1
+ import chalk from 'chalk';
2
+ import { verifyManifest } from '../manifest.js';
3
+ export async function runVerify(opts) {
4
+ try {
5
+ const result = verifyManifest(opts.manifest, opts.logos);
6
+ if (result.ok) {
7
+ console.log(chalk.green(`\n ✓ All ${result.verified.length} assets verified — integrity intact.\n`));
8
+ return;
9
+ }
10
+ console.log(chalk.red('\n ✗ Integrity check failed.\n'));
11
+ if (result.changed.length > 0) {
12
+ console.log(chalk.red(' Changed (hash mismatch):'));
13
+ for (const f of result.changed) {
14
+ console.log(chalk.red(` - ${f}`));
15
+ }
16
+ }
17
+ if (result.added.length > 0) {
18
+ console.log(chalk.yellow('\n Added (not in manifest):'));
19
+ for (const f of result.added) {
20
+ console.log(chalk.yellow(` + ${f}`));
21
+ }
22
+ }
23
+ if (result.removed.length > 0) {
24
+ console.log(chalk.red('\n Removed (in manifest but missing):'));
25
+ for (const f of result.removed) {
26
+ console.log(chalk.red(` × ${f}`));
27
+ }
28
+ }
29
+ if (result.verified.length > 0) {
30
+ console.log(chalk.green(`\n ${result.verified.length} assets verified OK.`));
31
+ }
32
+ console.log('');
33
+ process.exit(1);
34
+ }
35
+ catch (err) {
36
+ console.error(chalk.red(`Error: ${err.message}`));
37
+ process.exit(1);
38
+ }
39
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * manifest.ts — SHA-256 integrity manifest for brand assets.
3
+ *
4
+ * The manifest is the trust foundation. It maps every logo file to its
5
+ * SHA-256 hash so you can verify nothing has been tampered with.
6
+ */
7
+ export interface AssetEntry {
8
+ hash: string;
9
+ size: number;
10
+ format: string;
11
+ }
12
+ export interface Manifest {
13
+ version: string;
14
+ generated: string;
15
+ algorithm: string;
16
+ assets: Record<string, AssetEntry>;
17
+ }
18
+ export interface VerifyResult {
19
+ verified: string[];
20
+ changed: string[];
21
+ added: string[];
22
+ removed: string[];
23
+ ok: boolean;
24
+ }
25
+ /** Compute SHA-256 hash of a file, returned as "sha256:<hex>" */
26
+ export declare function hashFile(filePath: string): string;
27
+ /** Generate a manifest from all files under logosDir */
28
+ export declare function generateManifest(logosDir: string): Manifest;
29
+ /** Write manifest to disk as pretty-printed JSON */
30
+ export declare function writeManifest(manifest: Manifest, outputPath: string): void;
31
+ /** Read manifest from disk */
32
+ export declare function readManifest(manifestPath: string): Manifest;
33
+ /** Verify current files against a stored manifest */
34
+ export declare function verifyManifest(manifestPath: string, logosDir: string): VerifyResult;
@@ -0,0 +1,94 @@
1
+ /**
2
+ * manifest.ts — SHA-256 integrity manifest for brand assets.
3
+ *
4
+ * The manifest is the trust foundation. It maps every logo file to its
5
+ * SHA-256 hash so you can verify nothing has been tampered with.
6
+ */
7
+ import { createHash } from 'node:crypto';
8
+ import { readFileSync, writeFileSync, existsSync, statSync } from 'node:fs';
9
+ import { join, extname } from 'node:path';
10
+ import { globSync } from 'glob';
11
+ const FORMAT_MAP = {
12
+ '.png': 'png',
13
+ '.jpg': 'jpeg',
14
+ '.jpeg': 'jpeg',
15
+ '.svg': 'svg',
16
+ '.webp': 'webp',
17
+ };
18
+ /** Compute SHA-256 hash of a file, returned as "sha256:<hex>" */
19
+ export function hashFile(filePath) {
20
+ const content = readFileSync(filePath);
21
+ const hex = createHash('sha256').update(content).digest('hex');
22
+ return `sha256:${hex}`;
23
+ }
24
+ /** Detect image format from extension */
25
+ function detectFormat(filePath) {
26
+ return FORMAT_MAP[extname(filePath).toLowerCase()] ?? 'unknown';
27
+ }
28
+ /** Generate a manifest from all files under logosDir */
29
+ export function generateManifest(logosDir) {
30
+ const files = globSync('**/*', { cwd: logosDir, nodir: true }).sort();
31
+ const assets = {};
32
+ for (const file of files) {
33
+ const fullPath = join(logosDir, file);
34
+ const key = `logos/${file}`.replace(/\\/g, '/');
35
+ assets[key] = {
36
+ hash: hashFile(fullPath),
37
+ size: statSync(fullPath).size,
38
+ format: detectFormat(fullPath),
39
+ };
40
+ }
41
+ return {
42
+ version: '1.0',
43
+ generated: new Date().toISOString(),
44
+ algorithm: 'sha256',
45
+ assets,
46
+ };
47
+ }
48
+ /** Write manifest to disk as pretty-printed JSON */
49
+ export function writeManifest(manifest, outputPath) {
50
+ writeFileSync(outputPath, JSON.stringify(manifest, null, 2) + '\n', 'utf-8');
51
+ }
52
+ /** Read manifest from disk */
53
+ export function readManifest(manifestPath) {
54
+ if (!existsSync(manifestPath)) {
55
+ throw new Error(`Manifest not found: ${manifestPath}`);
56
+ }
57
+ return JSON.parse(readFileSync(manifestPath, 'utf-8'));
58
+ }
59
+ /** Verify current files against a stored manifest */
60
+ export function verifyManifest(manifestPath, logosDir) {
61
+ const stored = readManifest(manifestPath);
62
+ const current = generateManifest(logosDir);
63
+ const storedKeys = new Set(Object.keys(stored.assets));
64
+ const currentKeys = new Set(Object.keys(current.assets));
65
+ const verified = [];
66
+ const changed = [];
67
+ const added = [];
68
+ const removed = [];
69
+ // Check files that exist now
70
+ for (const key of currentKeys) {
71
+ if (!storedKeys.has(key)) {
72
+ added.push(key);
73
+ }
74
+ else if (stored.assets[key].hash !== current.assets[key].hash) {
75
+ changed.push(key);
76
+ }
77
+ else {
78
+ verified.push(key);
79
+ }
80
+ }
81
+ // Check files that were in manifest but no longer exist
82
+ for (const key of storedKeys) {
83
+ if (!currentKeys.has(key)) {
84
+ removed.push(key);
85
+ }
86
+ }
87
+ return {
88
+ verified,
89
+ changed,
90
+ added,
91
+ removed,
92
+ ok: changed.length === 0 && added.length === 0 && removed.length === 0,
93
+ };
94
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * readme-parser.ts — Multi-gate logo detection for README img tags.
3
+ *
4
+ * Hard-won rules from real migration failures:
5
+ * 1. Never inject whitespace — replace ONLY the src value on the same line
6
+ * 2. shields.io URLs with &logo= are NOT brand logos — always exclude
7
+ * 3. Badge <img> tags inside <a> tags are NOT brand logos — always exclude
8
+ * 4. Standalone badges (not in <a>) with shields.io/actions/badge paths — exclude
9
+ * 5. <p><img> on the same line is a valid logo pattern — must match
10
+ * 6. 4+ spaces of indentation = markdown code block — never add indentation
11
+ */
12
+ export interface LogoMatch {
13
+ /** Line number (1-indexed) */
14
+ line: number;
15
+ /** The full line content */
16
+ content: string;
17
+ /** The extracted src value */
18
+ src: string;
19
+ }
20
+ /**
21
+ * Find all logo <img> tags in a README.
22
+ *
23
+ * Gate 1: Line must contain <img with src="..."
24
+ * Gate 2: Line must NOT be a badge (no <a>, no shields.io, no /badge, no actions/workflows)
25
+ * Gate 3: The src must contain "logo" (brand asset indicator)
26
+ */
27
+ export declare function findLogoImgTags(content: string): LogoMatch[];
28
+ /**
29
+ * Rewrite logo src values in a README.
30
+ *
31
+ * Only replaces src in lines that pass all three gates.
32
+ * Preserves indentation, attributes, and surrounding HTML exactly.
33
+ * Returns the modified content.
34
+ */
35
+ export declare function rewriteLogoSrc(content: string, newSrc: string): string;
@@ -0,0 +1,97 @@
1
+ /**
2
+ * readme-parser.ts — Multi-gate logo detection for README img tags.
3
+ *
4
+ * Hard-won rules from real migration failures:
5
+ * 1. Never inject whitespace — replace ONLY the src value on the same line
6
+ * 2. shields.io URLs with &logo= are NOT brand logos — always exclude
7
+ * 3. Badge <img> tags inside <a> tags are NOT brand logos — always exclude
8
+ * 4. Standalone badges (not in <a>) with shields.io/actions/badge paths — exclude
9
+ * 5. <p><img> on the same line is a valid logo pattern — must match
10
+ * 6. 4+ spaces of indentation = markdown code block — never add indentation
11
+ */
12
+ /** Patterns that identify a line as a badge, not a logo */
13
+ const BADGE_PATTERNS = [
14
+ /shields\.io/i,
15
+ /\/badge[./?]/i,
16
+ /actions\/workflows/i,
17
+ /img\.shields/i,
18
+ /badge\.svg/i,
19
+ ];
20
+ /** Returns true if this line is a badge, not a brand logo */
21
+ function isBadgeLine(line) {
22
+ // Any <a> tag wrapping the <img> = badge
23
+ if (/<a\s/i.test(line))
24
+ return true;
25
+ // Known badge URL patterns in the src
26
+ for (const pattern of BADGE_PATTERNS) {
27
+ if (pattern.test(line))
28
+ return true;
29
+ }
30
+ return false;
31
+ }
32
+ /** Returns true if the <img> src looks like a logo reference */
33
+ function isLogoSrc(src) {
34
+ // Must contain "logo" somewhere in the path
35
+ return /logo/i.test(src);
36
+ }
37
+ /**
38
+ * Extract the src value from an <img> tag on a line.
39
+ * Returns null if no <img> with src is found.
40
+ */
41
+ function extractImgSrc(line) {
42
+ const match = line.match(/<img\s[^>]*src="([^"]*)"/i);
43
+ return match ? match[1] : null;
44
+ }
45
+ /**
46
+ * Find all logo <img> tags in a README.
47
+ *
48
+ * Gate 1: Line must contain <img with src="..."
49
+ * Gate 2: Line must NOT be a badge (no <a>, no shields.io, no /badge, no actions/workflows)
50
+ * Gate 3: The src must contain "logo" (brand asset indicator)
51
+ */
52
+ export function findLogoImgTags(content) {
53
+ const lines = content.split('\n');
54
+ const matches = [];
55
+ for (let i = 0; i < lines.length; i++) {
56
+ const line = lines[i];
57
+ // Gate 1: must have an <img> with src
58
+ const src = extractImgSrc(line);
59
+ if (!src)
60
+ continue;
61
+ // Gate 2: must not be a badge
62
+ if (isBadgeLine(line))
63
+ continue;
64
+ // Gate 3: src must look like a logo
65
+ if (!isLogoSrc(src))
66
+ continue;
67
+ matches.push({
68
+ line: i + 1,
69
+ content: line,
70
+ src,
71
+ });
72
+ }
73
+ return matches;
74
+ }
75
+ /**
76
+ * Rewrite logo src values in a README.
77
+ *
78
+ * Only replaces src in lines that pass all three gates.
79
+ * Preserves indentation, attributes, and surrounding HTML exactly.
80
+ * Returns the modified content.
81
+ */
82
+ export function rewriteLogoSrc(content, newSrc) {
83
+ const lines = content.split('\n');
84
+ const result = [];
85
+ for (let i = 0; i < lines.length; i++) {
86
+ const line = lines[i];
87
+ const src = extractImgSrc(line);
88
+ if (src && !isBadgeLine(line) && isLogoSrc(src)) {
89
+ // Replace only the src value — nothing else on the line changes
90
+ result.push(line.replace(`src="${src}"`, `src="${newSrc}"`));
91
+ }
92
+ else {
93
+ result.push(line);
94
+ }
95
+ }
96
+ return result.join('\n');
97
+ }
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@mcptoolshop/brand",
3
+ "version": "0.1.0",
4
+ "description": "Centralized brand asset management — migration, audit, and integrity verification for GitHub orgs",
5
+ "type": "module",
6
+ "bin": {
7
+ "brand": "./dist/cli.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "README.md",
12
+ "LICENSE"
13
+ ],
14
+ "keywords": [
15
+ "brand",
16
+ "assets",
17
+ "logo",
18
+ "integrity",
19
+ "sha256",
20
+ "migration",
21
+ "github",
22
+ "cli"
23
+ ],
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/mcp-tool-shop-org/brand.git"
27
+ },
28
+ "homepage": "https://mcp-tool-shop-org.github.io/brand/",
29
+ "scripts": {
30
+ "build": "tsc",
31
+ "clean": "rimraf dist",
32
+ "typecheck": "tsc --noEmit",
33
+ "test": "vitest run",
34
+ "test:watch": "vitest",
35
+ "prepublishOnly": "npm run clean && npm run build"
36
+ },
37
+ "engines": {
38
+ "node": ">=18"
39
+ },
40
+ "author": "mcp-tool-shop",
41
+ "license": "MIT",
42
+ "dependencies": {
43
+ "commander": "^12.0.0",
44
+ "chalk": "^5.3.0",
45
+ "glob": "^11.0.0"
46
+ },
47
+ "devDependencies": {
48
+ "@types/node": "^20.0.0",
49
+ "rimraf": "^5.0.0",
50
+ "typescript": "^5.4.0",
51
+ "vitest": "^2.0.0"
52
+ }
53
+ }